diff --git a/.gitignore b/.gitignore index cd0a85b..ebd6797 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ sites/*/files sites/*/private vendor/* + +*.tar.gz diff --git a/sites/all/modules/contrib/admin/admin_menu/README.txt b/sites/all/modules/contrib/admin/admin_menu/README.txt index f66a61e..6fe556d 100644 --- a/sites/all/modules/contrib/admin/admin_menu/README.txt +++ b/sites/all/modules/contrib/admin/admin_menu/README.txt @@ -1,65 +1,84 @@ +CONTENTS OF THIS FILE +--------------------- --- SUMMARY -- + * Introduction + * Requirements + * Installation + * Configuration + * Customization + * Troubleshooting + * FAQ + * Maintainers + + +INTRODUCTION +------------ The Administration menu module displays the entire administrative menu tree (and most local tasks) in a drop-down menu, providing administrators one- or two-click access to most pages. Other modules may also add menu links to the menu using hook_admin_menu_output_alter(). -For a full description of the module, visit the project page: - http://drupal.org/project/admin_menu +For a full description of the project visit the project page: +http://drupal.org/project/admin_menu To submit bug reports and feature suggestions, or to track changes: - http://drupal.org/project/issues/admin_menu +http://drupal.org/project/issues/admin_menu --- REQUIREMENTS -- +REQUIREMENTS +------------ -None. +No special requirements --- INSTALLATION -- +INSTALLATION +------------ -* Install as usual, see http://drupal.org/node/70151 for further information. +Install as you would normally install a contributed Drupal. See: +https://drupal.org/documentation/install/modules-themes/modules-7 for further +information. -* You likely want to disable Toolbar module, since its output clashes with - Administration menu. + * You likely want to disable Toolbar module, since its output clashes with + Administration menu. --- CONFIGURATION -- +CONFIGURATION +------------- -* Configure user permissions in Administration » People » Permissions: + * Configure user permissions in Administration » People » Permissions: - - Use the administration pages and help (System module) + - Use the administration pages and help (System module) - The top-level administration categories require this permission to be - accessible. The administration menu will be empty unless this permission is - granted. + The top-level administration categories require this permission to be + accessible. The administration menu will be empty unless this permission is + granted. - - Access administration menu + - Access administration menu - Users in roles with the "Access administration menu" permission will see - the administration menu at the top of each page. + Users in roles with the "Access administration menu" permission will see + the administration menu at the top of each page. - - Display Drupal links + - Display Drupal links - Users in roles with the "Display drupal links" permission will receive - links to drupal.org issue queues for all enabled contributed modules. The - issue queue links appear under the administration menu icon. + Users in roles with the "Display drupal links" permission will receive + links to drupal.org issue queues for all enabled contributed modules. The + issue queue links appear under the administration menu icon. - Note that the menu items displayed in the administration menu depend on the - actual permissions of the viewing user. For example, the "People" menu item - is not displayed to a user who is not a member of a role with the "Administer - users" permission. + Note that the menu items displayed in the administration menu depend on the + actual permissions of the viewing user. For example, the "People" menu item + is not displayed to a user who is not a member of a role with the + "Administer users" permission. -* Customize the menu settings in Administration » Configuration and modules » - Administration » Administration menu. + * Customize the menu settings in Administration » Configuration and modules » + Administration » Administration menu. -* To prevent administrative menu items from appearing twice, you may hide the - "Management" menu block. + * To prevent administrative menu items from appearing twice, you may hide the + "Management" menu block. --- CUSTOMIZATION -- +CUSTOMIZATION +------------- * To override the default administration menu icon, you may: @@ -82,12 +101,13 @@ None. body #admin-menu { font-size: 10px; } --- TROUBLESHOOTING -- +TROUBLESHOOTING +------------- * If the menu does not display, check the following: - - Are the "Access administration menu" and "Use the administration pages and help" - permissions enabled for the appropriate roles? + - Are the "Access administration menu" and "Use the administration pages and + help" permissions enabled for the appropriate roles? - Does html.tpl.php of your theme output the $page_bottom variable? @@ -99,90 +119,102 @@ None. See http://drupal.org/node/195386 for further information. --- FAQ -- +FAQ +--- -Q: When the administration menu module is enabled, blank space is added to the - bottom of my theme. Why? + Q: When the administration menu module is enabled, blank space is added to the + bottom of my theme. Why? -A: This is caused by a long list of links to module issue queues at Drupal.org. - Use Administer >> User management >> Permissions to disable the "display - drupal links" permission for all appropriate roles. Note that since UID 1 - automatically receives all permissions, the list of issue queue links cannot - be disabled for UID 1. + A: This is caused by a long list of links to module issue queues at Drupal.org. + Use Administer >> User management >> Permissions to disable the "display + drupal links" permission for all appropriate roles. Note that since UID 1 + automatically receives all permissions, the list of issue queue links cannot + be disabled for UID 1. -Q: After upgrading to 6.x-1.x, the menu disappeared. Why? + Q: After upgrading to 6.x-1.x, the menu disappeared. Why? -A: You may need to regenerate your menu. Visit - http://example.com/admin/build/modules to regenerate your menu (substitute - your site name for example.com). + A: You may need to regenerate your menu. Visit + http://example.com/admin/build/modules to regenerate your menu (substitute + your site name for example.com). -Q: Can I configure the administration menu module to display another menu (like - the Navigation menu, for instance)? + Q: Can I configure the administration menu module to display another menu (like + the Navigation menu, for instance)? -A: No. As the name implies, administration menu module is for administrative - menu links only. However, you can copy and paste the contents of - admin_menu.css into your theme's stylesheet and replace #admin-menu with any - other menu block id (#block-menu-1, for example). + A: No. As the name implies, administration menu module is for administrative + menu links only. However, you can copy and paste the contents of + admin_menu.css into your theme's stylesheet and replace #admin-menu with any + other menu block id (#block-menu-1, for example). -Q: Sometimes, the user counter displays a lot of anonymous users, but no spike - of users or requests appear in Google Analytics or other tracking tools. + Q: Sometimes, the user counter displays a lot of anonymous users, but no spike + of users or requests appear in Google Analytics or other tracking tools. -A: If your site was concurrently spidered by search-engine robots, it may have - a significant number of anonymous users for a short time. Most web tracking - tools like Google Analytics automatically filter out these requests. + A: If your site was concurrently spidered by search-engine robots, it may have + a significant number of anonymous users for a short time. Most web tracking + tools like Google Analytics automatically filter out these requests. -Q: I enabled "Aggregate and compress CSS files", but admin_menu.css is still - there. Is this normal? + Q: I enabled "Aggregate and compress CSS files", but admin_menu.css is still + there. Is this normal? -A: Yes, this is the intended behavior. the administration menu module only loads - its stylesheet as needed (i.e., on page requests by logged-on, administrative - users). + A: Yes, this is the intended behavior. the administration menu module only + loads its stylesheet as needed (i.e., on page requests by logged-on, + administrative users). -Q: Why are sub-menus not visible in Opera? + Q: Why are sub-menus not visible in Opera? -A: In the Opera browser preferences under "web pages" there is an option to fit - to width. By disabling this option, sub-menus in the administration menu - should appear. + A: In the Opera browser preferences under "web pages" there is an option to fit + to width. By disabling this option, sub-menus in the administration menu + should appear. -Q: How can the administration menu be hidden on certain pages? + Q: How can the administration menu be hidden on certain pages? -A: You can suppress it by simply calling the following function in PHP: + A: You can suppress it by simply calling the following function in PHP: + module_invoke('admin_menu', 'suppress'); - module_invoke('admin_menu', 'suppress'); - - However, this needs to happen as early as possible in the page request, so - placing it in the theming layer (resp. a page template file) is too late. - Ideally, the function is called in hook_init() in a custom module. If you do - not have a custom module, placing it into some conditional code at the top of - template.php may work out, too. + However, this needs to happen as early as possible in the page request, so + placing it in the theming layer (resp. a page template file) is too late. + Ideally, the function is called in hook_init() in a custom module. If you + do not have a custom module, placing it into some conditional code at the + top of template.php may work out, too. --- CONTACT -- +Q: What does the "Administration Development Tools" module do? + +A: The Administration Development Tools adds a jQuery Debugger which allows + a developer to debug and inspect arbitrary data/variables in Firebug's + console, and also to access them again in the global window object + (optionally using a named identifier, e.g. window.debug.myValue). + Chainable via jQuery. Especially useful for re-accessing and debugging + selected data via Firebug's console. + + +MAINTAINERS +----------- Current maintainers: -* Daniel F. Kudwien (sun) - http://drupal.org/user/54136 -* Peter Wolanin (pwolanin) - http://drupal.org/user/49851 -* Stefan M. Kudwien (smk-ka) - http://drupal.org/user/48898 -* Dave Reid (Dave Reid) - http://drupal.org/user/53892 + * Daniel F. Kudwien (sun) - http://drupal.org/user/54136 + * Peter Wolanin (pwolanin) - http://drupal.org/user/49851 + * Stefan M. Kudwien (smk-ka) - http://drupal.org/user/48898 + * Dave Reid (Dave Reid) - http://drupal.org/user/53892 + * Truls S. Yggeseth (truls1502) - http://drupal.org/user/325866 + * Sebastian Siemssen (fubhy) - https://www.drupal.org/user/761344 Major rewrite for Drupal 6 by Peter Wolanin (pwolanin). This project has been sponsored by: -* UNLEASHED MIND - Specialized in consulting and planning of Drupal powered sites, UNLEASHED - MIND offers installation, development, theming, customization, and hosting - to get you started. Visit http://www.unleashedmind.com for more information. + * UNLEASHED MIND + Specialized in consulting and planning of Drupal powered sites, UNLEASHED + MIND offers installation, development, theming, customization, and hosting + to get you started. Visit http://www.unleashedmind.com for more information. -* Lullabot - Friendly Drupal experts providing professional consulting & education - services. Visit http://www.lullabot.com for more information. - -* Acquia - Commercially Supported Drupal. Visit http://acquia.com for more information. + * Lullabot + Friendly Drupal experts providing professional consulting & education + services. Visit http://www.lullabot.com for more information. + * Acquia + Commercially Supported Drupal. Visit http://acquia.com for more information. diff --git a/sites/all/modules/contrib/admin/admin_menu/admin_devel/admin_devel.info b/sites/all/modules/contrib/admin/admin_menu/admin_devel/admin_devel.info index 6408a1b..8b09eb1 100644 --- a/sites/all/modules/contrib/admin/admin_menu/admin_devel/admin_devel.info +++ b/sites/all/modules/contrib/admin/admin_menu/admin_devel/admin_devel.info @@ -4,9 +4,8 @@ package = Administration core = 7.x scripts[] = admin_devel.js -; Information added by drupal.org packaging script on 2013-01-31 -version = "7.x-3.0-rc4" +; Information added by Drupal.org packaging script on 2018-12-03 +version = "7.x-3.0-rc6" core = "7.x" project = "admin_menu" -datestamp = "1359651687" - +datestamp = "1543859284" diff --git a/sites/all/modules/contrib/admin/admin_menu/admin_menu-rtl.css b/sites/all/modules/contrib/admin/admin_menu/admin_menu-rtl.css index 9414dcf..ff9f500 100644 --- a/sites/all/modules/contrib/admin/admin_menu/admin_menu-rtl.css +++ b/sites/all/modules/contrib/admin/admin_menu/admin_menu-rtl.css @@ -1,4 +1,3 @@ - #admin-menu { text-align: right; } @@ -25,7 +24,7 @@ border-right: 0; } #admin-menu .dropdown .admin-menu-tab a { - border-left: 1px solid #52565E; + border-left: 1px solid #52565e; border-right: 0; } #admin-menu .dropdown li li a { @@ -42,13 +41,13 @@ /* Second-level lists */ #admin-menu .dropdown li ul { left: auto; - right: -999em; + right: auto; } /* Third-and-above-level lists */ #admin-menu .dropdown li li.expandable ul { - margin-left: 0; - margin-right: 160px; + margin-left: 0 !important; + margin-right: 160px !important; } /* Lists nested under hovered list items */ diff --git a/sites/all/modules/contrib/admin/admin_menu/admin_menu.admin.js b/sites/all/modules/contrib/admin/admin_menu/admin_menu.admin.js index 9ee9f36..3753463 100644 --- a/sites/all/modules/contrib/admin/admin_menu/admin_menu.admin.js +++ b/sites/all/modules/contrib/admin/admin_menu/admin_menu.admin.js @@ -1,4 +1,8 @@ -(function($) { +/** + * @file + */ + +(function ($) { /** * Live preview of Administration menu components. @@ -45,9 +49,9 @@ Drupal.behaviors.adminMenuPermissionsSetupHelp = { // Figure out which is the other, check whether it still disabled, // and if so, ask whether to auto-enable it. var other = (this == $admin[index] ? $menu[index] : $admin[index]); - if (!other.checked && confirm(Drupal.t('Also allow !name role to !permission?', { - '!name': $roles[index].textContent, - '!permission': (this == $admin[index] ? menuPermission : adminPermission) + if (!other.checked && confirm(Drupal.t('Also allow @name role to @permission?', { + '@name': $roles[index].textContent, + '@permission': (this == $admin[index] ? menuPermission : adminPermission) }))) { other.checked = 'checked'; } diff --git a/sites/all/modules/contrib/admin/admin_menu/admin_menu.color.css b/sites/all/modules/contrib/admin/admin_menu/admin_menu.color.css index f2491cd..00cbf65 100644 --- a/sites/all/modules/contrib/admin/admin_menu/admin_menu.color.css +++ b/sites/all/modules/contrib/admin/admin_menu/admin_menu.color.css @@ -1,4 +1,3 @@ - /** * @file * Administration menu color override. @@ -17,7 +16,7 @@ border-right-color: #a91f1f; } #admin-menu ul li.admin-menu-tab a { - border-right-color: #52565E; + border-right-color: #52565e; } #admin-menu li li a { border-top-color: #801f1f; @@ -32,8 +31,7 @@ #admin-menu li li.expandable { background-color: #b93f3f; } -#admin-menu li li:hover, -#admin-menu li li.iehover { +#admin-menu li li:hover { background-color: #690f0f; } #admin-menu li li.expandable:hover a, diff --git a/sites/all/modules/contrib/admin/admin_menu/admin_menu.css b/sites/all/modules/contrib/admin/admin_menu/admin_menu.css index fd86dd7..73f15e3 100644 --- a/sites/all/modules/contrib/admin/admin_menu/admin_menu.css +++ b/sites/all/modules/contrib/admin/admin_menu/admin_menu.css @@ -1,4 +1,3 @@ - /** * @file * Administration menu. @@ -16,6 +15,7 @@ position: absolute; text-align: left; top: 0; + height: 30px; width: 100%; } #admin-menu-wrapper { @@ -62,7 +62,7 @@ body.admin-menu { #admin-menu li > span { background: transparent none; border: none; - color: #EEE; + color: #eee; font-weight: normal; text-align: left; /* LTR */ text-decoration: none; @@ -74,7 +74,7 @@ body.admin-menu { padding: 4px 8px; } #admin-menu .dropdown .admin-menu-tab a { - border-right: 1px solid #52565E; /* LTR */ + border-right: 1px solid #52565e; /* LTR */ } #admin-menu .dropdown li li a { border-right: none; /* LTR */ @@ -98,6 +98,7 @@ body.admin-menu { #admin-menu .dropdown li li { background: #202020; filter: Alpha(opacity=88); + float: none; opacity: 0.88; width: 160px; /* Required for Opera */ } @@ -126,11 +127,7 @@ body.admin-menu { #admin-menu .dropdown li:hover ul ul, #admin-menu .dropdown li:hover ul ul ul, #admin-menu .dropdown li:hover ul ul ul ul, -#admin-menu .dropdown li:hover ul ul ul ul ul, -#admin-menu .dropdown li.iehover ul ul, -#admin-menu .dropdown li.iehover ul ul ul, -#admin-menu .dropdown li.iehover ul ul ul ul, -#admin-menu .dropdown li.iehover ul ul ul ul ul { +#admin-menu .dropdown li:hover ul ul ul ul ul { display: none; left: -999em; /* LTR */ } @@ -140,12 +137,7 @@ body.admin-menu { #admin-menu .dropdown li li:hover ul, #admin-menu .dropdown li li li:hover ul, #admin-menu .dropdown li li li li:hover ul, -#admin-menu .dropdown li li li li li:hover ul, -#admin-menu .dropdown li.iehover ul, -#admin-menu .dropdown li li.iehover ul, -#admin-menu .dropdown li li li.iehover ul, -#admin-menu .dropdown li li li li.iehover ul, -#admin-menu .dropdown li li li li li.iehover ul { +#admin-menu .dropdown li li li li li:hover ul { display: block; left: auto; /* LTR */ } @@ -155,36 +147,27 @@ body.admin-menu { /* Second-and-more-level hovering */ #admin-menu .dropdown li li.expandable { - background: #45454A url(images/arrow.png) no-repeat 145px 6px; + background: #45454a url(images/arrow.png) no-repeat 145px 6px; } -#admin-menu .dropdown li li:hover, -#admin-menu .dropdown li li.iehover { +#admin-menu .dropdown li li:hover { background-color: #111; } #admin-menu .dropdown li li:hover a, #admin-menu .dropdown li li:hover li:hover a, #admin-menu .dropdown li li:hover li:hover li:hover a { - color: #FFF; + color: #fff; } #admin-menu .dropdown li li.expandable:hover a, #admin-menu .dropdown li li.expandable:hover li.expandable:hover a { border-color: #444; - color: #EEE; + color: #eee; } #admin-menu .dropdown li li.expandable:hover li a, #admin-menu .dropdown li li.expandable:hover li.expandable:hover li a { border-color: #323232; } -#admin-menu .dropdown li li:hover li a, -#admin-menu .dropdown li li.iehover li a, -#admin-menu .dropdown li li.iehover li.iehover li a { - color: #EEE; -} -#admin-menu .dropdown li li.iehover a, -#admin-menu .dropdown li li.iehover li.iehover a, -#admin-menu .dropdown li li.iehover li.iehover li.iehover a { - color: #FFF; - width: 90%; /* IE */ +#admin-menu .dropdown li li:hover li a { + color: #eee; } /* Search form */ diff --git a/sites/all/modules/contrib/admin/admin_menu/admin_menu.inc b/sites/all/modules/contrib/admin/admin_menu/admin_menu.inc index e212970..5721d9a 100644 --- a/sites/all/modules/contrib/admin/admin_menu/admin_menu.inc +++ b/sites/all/modules/contrib/admin/admin_menu/admin_menu.inc @@ -97,7 +97,7 @@ function admin_menu_tree_dynamic(array $expand_map) { $db_or = db_or(); foreach ($plids as $path_plids) { $db_and = db_and(); - // plids with value 0 may be ignored. + // Plids with value 0 may be ignored. foreach (array_filter($path_plids) as $column => $plid) { $db_and->condition($column, $plid); } @@ -204,7 +204,7 @@ function admin_menu_merge_tree(array &$tree, array $tree_dynamic, array $expand_ foreach ($load_functions as $index => $function) { if ($function) { if (is_array($function)) { - list($function,) = each($function); + $function = key($function); } // Add the loader function name minus "_load". $placeholder = '%' . substr($function, 0, -5); @@ -269,7 +269,7 @@ function admin_menu_translate($router_item, $map) { // replace any other. // @todo Doing this instead leads to plenty of duplicate links below // admin/structure/menu; likely a hidden recursion problem. - // $router_item['mlid'] = $router_item['href'] . $router_item['mlid']; + // $router_item['mlid'] = $router_item['href'] . $router_item['mlid'];. $router_item['mlid'] = $router_item['href']; // Turn menu callbacks into regular menu items to make them visible. if ($router_item['type'] == MENU_CALLBACK) { @@ -278,10 +278,12 @@ function admin_menu_translate($router_item, $map) { // @see _menu_tree_check_access() $key = (50000 + $router_item['weight']) . ' ' . $router_item['title'] . ' ' . $router_item['mlid']; - return array($key => array( - 'link' => $router_item, - 'below' => array(), - )); + return array( + $key => array( + 'link' => $router_item, + 'below' => array(), + ), + ); } return array(); @@ -461,20 +463,22 @@ function admin_menu_links_icon() { '#access' => user_access('display drupal links'), '#href' => 'http://drupal.org', ); - // Add links to project issue queues. - foreach (module_list(FALSE, TRUE) as $module) { - $info = drupal_parse_info_file(drupal_get_path('module', $module) . '/' . $module . '.info'); - if (!isset($info['project']) || isset($links['icon']['drupal.org'][$info['project']])) { - continue; + if (variable_get('admin_menu_issue_queues', TRUE)) { + // Add links to project issue queues. + foreach (module_list(FALSE, TRUE) as $module) { + $info = drupal_parse_info_file(drupal_get_path('module', $module) . '/' . $module . '.info'); + if (!isset($info['project']) || isset($links['icon']['drupal.org'][$info['project']])) { + continue; + } + $links['icon']['drupal.org'][$info['project']] = array( + '#title' => t('@project issue queue', array('@project' => $info['name'])), + '#weight' => ($info['project'] == 'drupal' ? -10 : 0), + '#href' => 'http://drupal.org/project/issues/' . $info['project'], + '#options' => array( + 'query' => array('version' => (isset($info['core']) ? $info['core'] : 'All')), + ), + ); } - $links['icon']['drupal.org'][$info['project']] = array( - '#title' => t('@project issue queue', array('@project' => $info['name'])), - '#weight' => ($info['project'] == 'drupal' ? -10 : 0), - '#href' => 'http://drupal.org/project/issues/' . $info['project'], - '#options' => array( - 'query' => array('version' => (isset($info['core']) ? $info['core'] : 'All')), - ), - ); } // Add items to flush caches. $links['icon']['flush-cache'] = array( @@ -497,18 +501,6 @@ function admin_menu_links_icon() { ); } - // Add link to toggle developer modules (performance). - $saved_state = variable_get('admin_menu_devel_modules_enabled', NULL); - $links['icon']['toggle-modules'] = array( - '#title' => isset($saved_state) ? t('Enable developer modules') : t('Disable developer modules'), - '#weight' => 88, - '#access' => user_access('administer modules'), - '#href' => 'admin_menu/toggle-modules', - '#options' => array( - 'query' => $destination + array('token' => drupal_get_token('admin_menu/toggle-modules')), - ), - ); - // Add Devel module menu links. if (module_exists('devel')) { $devel_tree = menu_build_tree('devel'); @@ -582,7 +574,7 @@ function admin_menu_links_users() { '#description' => t('Current anonymous / authenticated users'), '#weight' => -90, '#attributes' => array('class' => array('admin-menu-action', 'admin-menu-users')), - '#href' => (user_access('administer users') ? 'admin/people/people' : 'user'), + '#href' => (user_access('administer users') ? 'admin/people' : 'user'), ); return $links; } @@ -670,7 +662,7 @@ function admin_menu_theme_settings() { '#default_value' => variable_get('admin_menu_tweak_modules', 0), ); if (module_exists('util')) { - $form['tweaks']['admin_menu_tweak_modules']['#description'] .= '
' . t('If the Utility module was installed for this purpose, it can be safely disabled and uninstalled.') . ''; + $form['tweaks']['admin_menu_tweak_modules']['#description'] = '
' . t('If the Utility module was installed for this purpose, it can be safely disabled and uninstalled.') . ''; } $form['tweaks']['admin_menu_tweak_permissions'] = array( '#type' => 'checkbox', @@ -697,28 +689,10 @@ function admin_menu_theme_settings() { '#title' => t('Cache menu in client-side browser'), '#default_value' => variable_get('admin_menu_cache_client', 1), ); - // Fetch all available modules manually, since module_list() only returns - // currently enabled modules, which makes this setting pointless if developer - // modules are currently disabled. - $all_modules = array(); - $result = db_query("SELECT name, filename, info FROM {system} WHERE type = 'module' ORDER BY name ASC"); - foreach ($result as $module) { - if (file_exists($module->filename)) { - $info = unserialize($module->info); - $all_modules[$module->name] = $info['name']; - } - } - $devel_modules = variable_get('admin_menu_devel_modules', _admin_menu_developer_modules()); - $devel_modules = array_intersect_key($all_modules, array_flip($devel_modules)); - $form['performance']['admin_menu_devel_modules_skip'] = array( - '#type' => 'checkboxes', - '#title' => t('Developer modules to keep enabled'), - '#default_value' => variable_get('admin_menu_devel_modules_skip', array()), - '#options' => $devel_modules, - '#access' => !empty($devel_modules), - '#description' => t('The selected modules will not be disabled when the link %disable-developer-modules below the icon in the menu is invoked.', array( - '%disable-developer-modules' => t('Disable developer modules'), - )), + $form['performance']['admin_menu_issue_queues'] = array( + '#type' => 'checkbox', + '#title' => t('Show Issue Queue links in icon menu'), + '#default_value' => variable_get('admin_menu_issue_queues', 1), ); return system_settings_form($form); @@ -783,94 +757,6 @@ function _admin_menu_form_devel_admin_settings_alter(&$form, $form_state) { ); } -/** - * Menu callback; Enable/disable developer modules. - * - * This can save up to 150ms on each uncached page request. - */ -function admin_menu_toggle_modules() { - if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], current_path())) { - return MENU_ACCESS_DENIED; - } - - $rebuild = FALSE; - $saved_state = variable_get('admin_menu_devel_modules_enabled', NULL); - if (isset($saved_state)) { - // Re-enable modules that were enabled before. - module_enable($saved_state); - variable_del('admin_menu_devel_modules_enabled'); - drupal_set_message(t('Enabled these modules: !module-list.', array('!module-list' => implode(', ', $saved_state)))); - $rebuild = TRUE; - } - else { - // Allow site admins to override this variable via settings.php. - $devel_modules = variable_get('admin_menu_devel_modules', _admin_menu_developer_modules()); - // Store currently enabled modules in a variable. - $devel_modules = array_intersect(module_list(FALSE, FALSE), $devel_modules); - $devel_modules = array_diff($devel_modules, variable_get('admin_menu_devel_modules_skip', array())); - if (!empty($devel_modules)) { - variable_set('admin_menu_devel_modules_enabled', $devel_modules); - // Disable developer modules. - module_disable($devel_modules); - drupal_set_message(t('Disabled these modules: !module-list.', array('!module-list' => implode(', ', $devel_modules)))); - $rebuild = TRUE; - } - else { - drupal_set_message(t('No developer modules are enabled.')); - } - } - if ($rebuild) { - // Make sure everything is rebuilt, basically a combination of the calls - // from system_modules() and system_modules_submit(). - drupal_theme_rebuild(); - menu_rebuild(); - cache_clear_all('schema', 'cache'); - cache_clear_all(); - drupal_clear_css_cache(); - drupal_clear_js_cache(); - // Synchronize to catch any actions that were added or removed. - actions_synchronize(); - // Finally, flush admin_menu's cache. - admin_menu_flush_caches(); - } - drupal_goto(); -} - -/** - * Helper function to return a default list of developer modules. - */ -function _admin_menu_developer_modules() { - return array( - 'admin_devel', - 'cache_disable', - 'coder', - 'content_copy', - 'context_ui', - 'debug', - 'delete_all', - 'demo', - 'devel', - 'devel_node_access', - 'devel_themer', - 'field_ui', - 'fontyourface_ui', - 'form_controller', - 'imagecache_ui', - 'journal', - 'l10n_client', - 'l10n_update', - 'macro', - 'rules_admin', - 'stringoverrides', - 'trace', - 'upgrade_status', - 'user_display_ui', - 'util', - 'views_ui', - 'views_theme_wizard', - ); -} - /** * Flush all caches or a specific one. * @@ -886,18 +772,20 @@ function admin_menu_flush_cache($name = NULL) { if (!isset($caches[$name])) { return MENU_NOT_FOUND; } + $message = t('@title cache cleared.', array('@title' => $caches[$name]['title'])); } else { $caches[$name] = array( 'title' => t('Every'), 'callback' => 'drupal_flush_all_caches', ); + $message = t('All caches cleared.'); } // Pass the cache to flush forward to the callback. $function = $caches[$name]['callback']; $function($name); - drupal_set_message(t('!title cache cleared.', array('!title' => $caches[$name]['title']))); + drupal_set_message($message); // The JavaScript injects a destination request parameter pointing to the // originating page, so the user is redirected back to that page. Without @@ -1030,4 +918,3 @@ function template_preprocess_admin_menu_icon(&$variables) { function theme_admin_menu_icon($variables) { return '' . $variables['alt'] . ''; } - diff --git a/sites/all/modules/contrib/admin/admin_menu/admin_menu.info b/sites/all/modules/contrib/admin/admin_menu/admin_menu.info index ee58c66..ffd22e4 100644 --- a/sites/all/modules/contrib/admin/admin_menu/admin_menu.info +++ b/sites/all/modules/contrib/admin/admin_menu/admin_menu.info @@ -8,9 +8,8 @@ configure = admin/config/administration/admin_menu dependencies[] = system (>7.10) files[] = tests/admin_menu.test -; Information added by drupal.org packaging script on 2013-01-31 -version = "7.x-3.0-rc4" +; Information added by Drupal.org packaging script on 2018-12-03 +version = "7.x-3.0-rc6" core = "7.x" project = "admin_menu" -datestamp = "1359651687" - +datestamp = "1543859284" diff --git a/sites/all/modules/contrib/admin/admin_menu/admin_menu.install b/sites/all/modules/contrib/admin/admin_menu/admin_menu.install index 70e31c6..4e5443d 100644 --- a/sites/all/modules/contrib/admin/admin_menu/admin_menu.install +++ b/sites/all/modules/contrib/admin/admin_menu/admin_menu.install @@ -33,9 +33,6 @@ function admin_menu_install() { function admin_menu_uninstall() { // Delete variables. variable_del('admin_menu_components'); - variable_del('admin_menu_devel_modules'); - variable_del('admin_menu_devel_modules_enabled'); - variable_del('admin_menu_devel_modules_skip'); variable_del('admin_menu_margin_top'); variable_del('admin_menu_position_fixed'); variable_del('admin_menu_tweak_modules'); @@ -44,6 +41,10 @@ function admin_menu_uninstall() { variable_del('admin_menu_display'); variable_del('admin_menu_cache_server'); variable_del('admin_menu_cache_client'); + // Unused variables still should be deleted. + variable_del('admin_menu_devel_modules'); + variable_del('admin_menu_devel_modules_enabled'); + variable_del('admin_menu_devel_modules_skip'); } /** diff --git a/sites/all/modules/contrib/admin/admin_menu/admin_menu.js b/sites/all/modules/contrib/admin/admin_menu/admin_menu.js index de0bb90..1e32b3d 100644 --- a/sites/all/modules/contrib/admin/admin_menu/admin_menu.js +++ b/sites/all/modules/contrib/admin/admin_menu/admin_menu.js @@ -1,4 +1,8 @@ -(function($) { +/** + * @file + */ + +(function ($) { Drupal.admin = Drupal.admin || {}; Drupal.admin.behaviors = Drupal.admin.behaviors || {}; @@ -139,7 +143,7 @@ Drupal.admin.getCache = function (hash, onSuccess) { * * @see toolbar.js */ -Drupal.admin.height = function() { +Drupal.admin.height = function () { var $adminMenu = $('#admin-menu'); var height = $adminMenu.outerHeight(); // In IE, Shadow filter adds some extra height, so we need to remove it from @@ -161,7 +165,7 @@ Drupal.admin.height = function() { Drupal.admin.attachBehaviors = function (context, settings, $adminMenu) { if ($adminMenu.length) { $adminMenu.addClass('admin-menu-processed'); - $.each(Drupal.admin.behaviors, function() { + $.each(Drupal.admin.behaviors, function () { this(context, settings, $adminMenu); }); } @@ -206,7 +210,7 @@ Drupal.admin.behaviors.replacements = function (context, settings, $adminMenu) { */ Drupal.admin.behaviors.destination = function (context, settings, $adminMenu) { if (settings.admin_menu.destination) { - $('a.admin-menu-destination', $adminMenu).each(function() { + $('a.admin-menu-destination', $adminMenu).each(function () { this.search += (!this.search.length ? '?' : '&') + Drupal.settings.admin_menu.destination; }); } @@ -219,18 +223,6 @@ Drupal.admin.behaviors.destination = function (context, settings, $adminMenu) { * it will not run last. */ Drupal.admin.behaviors.hover = function (context, settings, $adminMenu) { - // Hover emulation for IE 6. - if ($.browser.msie && parseInt(jQuery.browser.version) == 6) { - $('li', $adminMenu).hover( - function () { - $(this).addClass('iehover'); - }, - function () { - $(this).removeClass('iehover'); - } - ); - } - // Delayed mouseout. $('li.expandable', $adminMenu).hover( function () { diff --git a/sites/all/modules/contrib/admin/admin_menu/admin_menu.map.inc b/sites/all/modules/contrib/admin/admin_menu/admin_menu.map.inc index 72a8960..831e324 100644 --- a/sites/all/modules/contrib/admin/admin_menu/admin_menu.map.inc +++ b/sites/all/modules/contrib/admin/admin_menu/admin_menu.map.inc @@ -58,68 +58,59 @@ function node_admin_menu_map() { /** * Implements hook_admin_menu_map() on behalf of Field UI module. + * + * @todo Figure out how to fix the comment entity bundle mappings. */ function field_ui_admin_menu_map() { $map = array(); - foreach (entity_get_info() as $obj_type => $info) { - foreach ($info['bundles'] as $bundle_name => $bundle_info) { - if (isset($bundle_info['admin'])) { - $arguments = array(); - switch ($obj_type) { - case 'comment': - $fields = array(); - foreach (field_info_instances($obj_type, $bundle_name) as $field) { - $fields[] = $field['field_name']; - } - // @todo Make Comment module expose the original node type bundle, - // pretty please. - if (drupal_substr($bundle_name, 0, 13) == 'comment_node_') { - $bundle_name = drupal_substr($bundle_name, 13); - } - // @todo Doesn't work yet. Why? - $arguments = array( - '%comment_node_type' => array($bundle_name), - '%field_ui_menu' => $fields, - ); - break; - case 'node': - $fields = array(); - foreach (field_info_instances($obj_type, $bundle_name) as $field) { - $fields[] = $field['field_name']; - } - $arguments = array( - '%node_type' => array($bundle_name), - '%field_ui_menu' => $fields, - ); - break; + foreach (entity_get_info() as $entity_type => $entity_info) { + if (!$entity_info['fieldable']) { + continue; + } - case 'taxonomy_term': - $fields = array(); - foreach (field_info_instances($obj_type, $bundle_name) as $field) { - $fields[] = $field['field_name']; - } - // Map machine_name to vid. - $arguments = array( - '%taxonomy_vocabulary_machine_name' => array($bundle_name), - '%field_ui_menu' => $fields, - ); - break; + foreach ($entity_info['bundles'] as $bundle => $bundle_info) { + // @see field_ui_menu() + if (!isset($bundle_info['admin'])) { + continue; + } - case 'user': - $arguments = array( - '%field_ui_menu' => array_keys(field_info_fields('user')), - ); - break; + // Check access to this bundle. + $bundle_info['admin'] += array( + 'access callback' => 'user_access', + 'access arguments' => array('administer site configuration'), + ); + $access_arguments = $bundle_info['admin']['access arguments']; + if (isset($bundle_info['admin']['real path'])) { + $menu_item = menu_get_item($bundle_info['admin']['real path']); + if (isset($menu_item['map'])) { + $access_arguments = menu_unserialize(serialize($access_arguments), $menu_item['map']); } - if (!empty($arguments)) { - $path = $bundle_info['admin']['path']; - $map["$path/fields/%field_ui_menu"]['parent'] = "$path/fields"; - $map["$path/fields/%field_ui_menu"]['arguments'][] = $arguments; + } + if (!call_user_func_array($bundle_info['admin']['access callback'], $access_arguments)) { + continue; + } + + if ($fields = field_info_instances($entity_type, $bundle)) { + $path = $bundle_info['admin']['path']; + $argument = array(); + if (isset($bundle_info['admin']['bundle argument'])) { + $bundle_arg = arg($bundle_info['admin']['bundle argument'], $path); + $argument[$bundle_arg] = array($bundle); } + $argument['%field_ui_menu'] = array_keys($fields); + + if (!isset($map["$path/fields/%field_ui_menu"])) { + $map["$path/fields/%field_ui_menu"] = array( + 'parent' => "$path/fields", + 'arguments' => array(), + ); + } + $map["$path/fields/%field_ui_menu"]['arguments'][] = $argument; } } } + return $map; } @@ -161,4 +152,3 @@ function views_ui_admin_menu_map() { ); return $map; } - diff --git a/sites/all/modules/contrib/admin/admin_menu/admin_menu.module b/sites/all/modules/contrib/admin/admin_menu/admin_menu.module index 8a8dee1..f9ce3ae 100644 --- a/sites/all/modules/contrib/admin/admin_menu/admin_menu.module +++ b/sites/all/modules/contrib/admin/admin_menu/admin_menu.module @@ -63,7 +63,7 @@ function admin_menu_theme() { function admin_menu_menu() { // AJAX callback. // @see http://drupal.org/project/js - $items['js/admin_menu/cache'] = array( + $items['js/admin_menu/cache/%'] = array( 'page callback' => 'admin_menu_js_cache', 'delivery callback' => 'admin_menu_deliver', 'access arguments' => array('access administration menu'), @@ -78,7 +78,7 @@ function admin_menu_menu() { 'file' => 'system.admin.inc', 'file path' => drupal_get_path('module', 'system'), ); - $items['admin/config/administration/admin_menu'] = array( + $items['admin/config/administration/admin-menu'] = array( 'title' => 'Administration menu', 'description' => 'Adjust administration menu settings.', 'page callback' => 'drupal_get_form', @@ -87,12 +87,6 @@ function admin_menu_menu() { 'file' => 'admin_menu.inc', ); // Menu link callbacks. - $items['admin_menu/toggle-modules'] = array( - 'page callback' => 'admin_menu_toggle_modules', - 'access arguments' => array('administer modules'), - 'type' => MENU_CALLBACK, - 'file' => 'admin_menu.inc', - ); $items['admin_menu/flush-cache'] = array( 'page callback' => 'admin_menu_flush_cache', 'access arguments' => array('flush caches'), @@ -217,7 +211,7 @@ function admin_menu_page_build(&$page) { // @todo Drupal.behaviors.adminMenuMarginTop is obsolete, but // hook_page_build() does not allow to set a CSS class on the body yet. // @see http://drupal.org/node/1473548, http://drupal.org/node/1194528 - //$page['#attributes']['class'][] = 'admin-menu'; + // $page['#attributes']['class'][] = 'admin-menu'; } if ($setting = variable_get('admin_menu_position_fixed', 1)) { $settings['position_fixed'] = $setting; @@ -236,7 +230,7 @@ function admin_menu_page_build(&$page) { if ($_GET['q'] == 'admin/modules' || strpos($_GET['q'], 'admin/modules/list') === 0) { $settings['tweak_modules'] = variable_get('admin_menu_tweak_modules', 0); } - if ($_GET['q'] == 'admin/people/permissions' || $_GET['q'] == 'admin/people/permissions/list') { + if (strpos($_GET['q'], 'admin/people/permissions') === 0) { $settings['tweak_permissions'] = variable_get('admin_menu_tweak_permissions', 0); } @@ -508,7 +502,6 @@ function admin_menu_output($complete = FALSE) { // @todo Move the below callbacks into hook_admin_menu_build() // implementations (and $module.admin_menu.inc). - // Add administration menu. if (!empty($components['admin_menu.menu']) || $complete) { $content['menu'] = admin_menu_links_menu(admin_menu_tree('management')); @@ -554,7 +547,10 @@ function admin_menu_output($complete = FALSE) { // Store the new hash for this user. if (!empty($_COOKIE['has_js']) && !$complete) { - admin_menu_cache_set($cid, md5($content)); + $cache = cache_get($cid, 'cache_admin_menu'); + if (!$cache || !isset($cache->data)) { + admin_menu_cache_set($cid, md5($content)); + } } return $content; @@ -609,11 +605,13 @@ function admin_menu_admin_menu_output_build(&$content) { * Implements hook_admin_menu_output_alter(). */ function admin_menu_admin_menu_output_alter(&$content) { - foreach ($content['menu'] as $key => $link) { - // Move local tasks on 'admin' into icon menu. - if ($key == 'admin/tasks' || $key == 'admin/index') { - $content['icon']['icon'][$key] = $link; - unset($content['menu'][$key]); + if (!empty($content['menu'])) { + foreach ($content['menu'] as $key => $link) { + // Move local tasks on 'admin' into icon menu. + if ($key == 'admin/tasks' || $key == 'admin/index') { + $content['icon']['icon'][$key] = $link; + unset($content['menu'][$key]); + } } } } @@ -683,6 +681,13 @@ function theme_admin_menu_links($variables) { $elements[$path]['#options']['attributes']['class'][] = 'admin-menu-destination'; } + // If the path has an alias replace the href with the alias. + if (module_exists('path')) { + if ($alias = drupal_get_path_alias($elements[$path]['#href'])) { + $elements[$path]['#href'] = $alias; + } + } + $link = l($elements[$path]['#title'], $elements[$path]['#href'], $elements[$path]['#options']); } // Handle plain text items, but do not interfere with menu additions. @@ -757,7 +762,7 @@ function admin_menu_translated_menu_link_alter(&$item, $map) { } } - // Don't waste cycles altering items that are not visible + // Don't waste cycles altering items that are not visible. if (!$item['access']) { return; } @@ -801,8 +806,8 @@ function admin_menu_flush_caches($uid = NULL) { cache_clear_all($cid, 'cache_menu', TRUE); // Flush client-side cache hashes. drupal_static_reset('admin_menu_cache_get'); - // db_table_exists() required for SimpleTest. - if (db_table_exists('cache_admin_menu')) { + // If cache_admin_menu is not empty, flush it. + if (!cache_is_empty('cache_admin_menu')) { cache_clear_all(isset($uid) ? $cid : '*', 'cache_admin_menu', TRUE); } } diff --git a/sites/all/modules/contrib/admin/admin_menu/admin_menu.uid1.css b/sites/all/modules/contrib/admin/admin_menu/admin_menu.uid1.css index e375cfe..105fd24 100644 --- a/sites/all/modules/contrib/admin/admin_menu/admin_menu.uid1.css +++ b/sites/all/modules/contrib/admin/admin_menu/admin_menu.uid1.css @@ -1,4 +1,3 @@ - /** * @file * Administration menu color override for uid1. diff --git a/sites/all/modules/contrib/admin/admin_menu/admin_menu_toolbar/admin_menu_toolbar-rtl.css b/sites/all/modules/contrib/admin/admin_menu/admin_menu_toolbar/admin_menu_toolbar-rtl.css new file mode 100644 index 0000000..a159034 --- /dev/null +++ b/sites/all/modules/contrib/admin/admin_menu/admin_menu_toolbar/admin_menu_toolbar-rtl.css @@ -0,0 +1,4 @@ +#admin-menu > div > .dropdown > li > a, +#admin-menu > div > .dropdown > li > span { + border-left: 0; +} diff --git a/sites/all/modules/contrib/admin/admin_menu/admin_menu_toolbar/admin_menu_toolbar.css b/sites/all/modules/contrib/admin/admin_menu/admin_menu_toolbar/admin_menu_toolbar.css index a3e9f3e..f1bc093 100644 --- a/sites/all/modules/contrib/admin/admin_menu/admin_menu_toolbar/admin_menu_toolbar.css +++ b/sites/all/modules/contrib/admin/admin_menu/admin_menu_toolbar/admin_menu_toolbar.css @@ -1,4 +1,3 @@ - /** * @file * Toolbar style for Administration menu. @@ -63,8 +62,9 @@ body div#toolbar.toolbar { background: url(toolbar.png) no-repeat 0 -45px; text-indent: -9999px; } -#admin-menu > div > .dropdown > li > a { - border-right: 0; +#admin-menu > div > .dropdown > li > a, +#admin-menu > div > .dropdown > li > span { + border-right: 0; /* LTR */ margin-bottom: 4px; padding: 2px 10px 3px; } @@ -142,4 +142,3 @@ body div#toolbar.toolbar { #admin-menu .shortcut-toolbar a { display: block; } - diff --git a/sites/all/modules/contrib/admin/admin_menu/admin_menu_toolbar/admin_menu_toolbar.info b/sites/all/modules/contrib/admin/admin_menu/admin_menu_toolbar/admin_menu_toolbar.info index 2514776..340ced0 100644 --- a/sites/all/modules/contrib/admin/admin_menu/admin_menu_toolbar/admin_menu_toolbar.info +++ b/sites/all/modules/contrib/admin/admin_menu/admin_menu_toolbar/admin_menu_toolbar.info @@ -4,9 +4,8 @@ package = Administration core = 7.x dependencies[] = admin_menu -; Information added by drupal.org packaging script on 2013-01-31 -version = "7.x-3.0-rc4" +; Information added by Drupal.org packaging script on 2018-12-03 +version = "7.x-3.0-rc6" core = "7.x" project = "admin_menu" -datestamp = "1359651687" - +datestamp = "1543859284" diff --git a/sites/all/modules/contrib/admin/admin_menu/admin_menu_toolbar/admin_menu_toolbar.install b/sites/all/modules/contrib/admin/admin_menu/admin_menu_toolbar/admin_menu_toolbar.install index bf067d7..f6449dc 100644 --- a/sites/all/modules/contrib/admin/admin_menu/admin_menu_toolbar/admin_menu_toolbar.install +++ b/sites/all/modules/contrib/admin/admin_menu/admin_menu_toolbar/admin_menu_toolbar.install @@ -34,4 +34,3 @@ function admin_menu_toolbar_update_6300() { ->condition('name', 'admin_menu_toolbar') ->execute(); } - diff --git a/sites/all/modules/contrib/admin/admin_menu/admin_menu_toolbar/admin_menu_toolbar.module b/sites/all/modules/contrib/admin/admin_menu/admin_menu_toolbar/admin_menu_toolbar.module index 74a9ee1..0f5c066 100644 --- a/sites/all/modules/contrib/admin/admin_menu/admin_menu_toolbar/admin_menu_toolbar.module +++ b/sites/all/modules/contrib/admin/admin_menu/admin_menu_toolbar/admin_menu_toolbar.module @@ -70,7 +70,7 @@ function admin_menu_toolbar_admin_menu_output_build(&$content) { '#title' => t('Show shortcuts'), '#href' => '', '#options' => array( - 'attributes' => array('class' => 'shortcut-toggle'), + 'attributes' => array('class' => array('shortcut-toggle')), ), ); @@ -115,4 +115,3 @@ function admin_menu_toolbar_admin_menu_output_alter(&$content) { $content['account']['account']['#options']['html'] = TRUE; } } - diff --git a/sites/all/modules/contrib/admin/admin_menu/tests/admin_menu.test b/sites/all/modules/contrib/admin/admin_menu/tests/admin_menu.test index 4b48c9a..b7c611d 100644 --- a/sites/all/modules/contrib/admin/admin_menu/tests/admin_menu.test +++ b/sites/all/modules/contrib/admin/admin_menu/tests/admin_menu.test @@ -16,7 +16,10 @@ class AdminMenuWebTestCase extends DrupalWebTestCase { 'admin_menu' => 'access administration menu', ); - function setUp() { + /** + * + */ + public function setUp() { // Enable admin menu module and any other modules. $modules = func_get_args(); $modules = isset($modules[0]) ? $modules[0] : $modules; @@ -108,12 +111,17 @@ class AdminMenuWebTestCase extends DrupalWebTestCase { $xpath = '//div[@id="admin-menu"]/div/ul' . implode('/parent::li/ul', $xpath); $this->assertNoElementByXPath($xpath, $args, $message . ' link not found.'); } + } /** * Tests menu links depending on user permissions. */ class AdminMenuPermissionsTestCase extends AdminMenuWebTestCase { + + /** + * + */ public static function getInfo() { return array( 'name' => 'Menu link access permissions', @@ -122,14 +130,17 @@ class AdminMenuPermissionsTestCase extends AdminMenuWebTestCase { ); } - function setUp() { + /** + * + */ + public function setUp() { parent::setUp(array('node')); } /** * Test that the links are added to the page (no JS testing). */ - function testPermissions() { + public function testPermissions() { module_enable(array('contact')); $this->resetAll(); @@ -140,7 +151,7 @@ class AdminMenuPermissionsTestCase extends AdminMenuWebTestCase { // Create a user who // - can access content overview // - cannot access drupal.org links - // - cannot administer Contact module + // - cannot administer Contact module. $permissions = $this->basePermissions + array( 'access content overview', ); @@ -156,7 +167,7 @@ class AdminMenuPermissionsTestCase extends AdminMenuWebTestCase { // Create a user "reversed" to the above; i.e., who // - cannot access content overview // - can access drupal.org links - // - can administer Contact module + // - can administer Contact module. $permissions = $this->basePermissions + array( 'display drupal links', 'administer contact forms', @@ -172,7 +183,7 @@ class AdminMenuPermissionsTestCase extends AdminMenuWebTestCase { /** * Tests handling of links pointing to category/overview pages. */ - function testCategories() { + public function testCategories() { // Create a user with minimum permissions. $admin_user = $this->drupalCreateUser($this->basePermissions); $this->drupalLogin($admin_user); @@ -201,7 +212,7 @@ class AdminMenuPermissionsTestCase extends AdminMenuWebTestCase { /** * Tests that user role and permission changes are properly taken up. */ - function testPermissionChanges() { + public function testPermissionChanges() { // Create a user who is able to change permissions. $permissions = $this->basePermissions + array( 'administer permissions', @@ -253,12 +264,17 @@ class AdminMenuPermissionsTestCase extends AdminMenuWebTestCase { // Verify that Structure » Content types does not appear. $this->assertNoLinkTrailByTitle(array(t('Structure'), t('Content types'))); } + } /** * Tests appearance, localization, and escaping of dynamic links. */ class AdminMenuDynamicLinksTestCase extends AdminMenuWebTestCase { + + /** + * + */ public static function getInfo() { return array( 'name' => 'Dynamic links', @@ -267,14 +283,17 @@ class AdminMenuDynamicLinksTestCase extends AdminMenuWebTestCase { ); } - function setUp() { + /** + * + */ + public function setUp() { parent::setUp(array('node')); } /** * Tests node type links. */ - function testNode() { + public function testNode() { $type = $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article')); // Create a content-type with special characters. $type = $this->drupalCreateContentType(array('type' => 'special', 'name' => 'Cool & Special')); @@ -324,7 +343,7 @@ class AdminMenuDynamicLinksTestCase extends AdminMenuWebTestCase { /** * Tests Add content links. */ - function testNodeAdd() { + public function testNodeAdd() { $type = $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article')); // Verify that "Add content" does not appear for unprivileged users. @@ -359,12 +378,17 @@ class AdminMenuDynamicLinksTestCase extends AdminMenuWebTestCase { t('Add content'), )); } + } /** * Tests appearance of different types of links. */ class AdminMenuLinkTypesTestCase extends AdminMenuWebTestCase { + + /** + * + */ public static function getInfo() { return array( 'name' => 'Link types', @@ -373,7 +397,10 @@ class AdminMenuLinkTypesTestCase extends AdminMenuWebTestCase { ); } - function setUp() { + /** + * + */ + public function setUp() { parent::setUp(array('help')); $permissions = module_invoke_all('permission'); @@ -385,7 +412,7 @@ class AdminMenuLinkTypesTestCase extends AdminMenuWebTestCase { /** * Tests appearance of different router item link types. */ - function testLinkTypes() { + public function testLinkTypes() { // Verify that MENU_NORMAL_ITEMs appear. $this->assertLinkTrailByTitle(array( t('Configuration'), @@ -420,12 +447,17 @@ class AdminMenuLinkTypesTestCase extends AdminMenuWebTestCase { ':title' => t('Index'), ), "Icon » Index link found."); } + } /** * Tests customized menu links. */ class AdminMenuCustomizedTestCase extends AdminMenuWebTestCase { + + /** + * + */ public static function getInfo() { return array( 'name' => 'Customized links', @@ -434,7 +466,10 @@ class AdminMenuCustomizedTestCase extends AdminMenuWebTestCase { ); } - function setUp() { + /** + * + */ + public function setUp() { parent::setUp(array('menu')); $this->admin_user = $this->drupalCreateUser($this->basePermissions + array( @@ -446,7 +481,7 @@ class AdminMenuCustomizedTestCase extends AdminMenuWebTestCase { /** * Test disabled custom links. */ - function testCustomDisabled() { + public function testCustomDisabled() { $type = $this->drupalCreateContentType(); $node = $this->drupalCreateNode(array('type' => $type->type)); $text = $this->randomName(); @@ -488,7 +523,7 @@ class AdminMenuCustomizedTestCase extends AdminMenuWebTestCase { /** * Tests external links. */ - function testCustomExternal() { + public function testCustomExternal() { // Add a custom link to the node to the menu. $edit = array( 'link_path' => 'http://example.com', @@ -516,5 +551,5 @@ class AdminMenuCustomizedTestCase extends AdminMenuWebTestCase { ':path' => $path, ))->fetchField(); } -} +} diff --git a/sites/all/modules/contrib/admin/admin_theme/LICENSE.txt b/sites/all/modules/contrib/admin/admin_theme/LICENSE.txt index 2c095c8..d159169 100644 --- a/sites/all/modules/contrib/admin/admin_theme/LICENSE.txt +++ b/sites/all/modules/contrib/admin/admin_theme/LICENSE.txt @@ -1,274 +1,339 @@ -GNU GENERAL PUBLIC LICENSE + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 - Version 2, June 1991 + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. -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 - Preamble + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. -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. -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. -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. -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. -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. -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. -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. -The precise terms and conditions for copying, distribution and modification -follow. + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - 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". -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. -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. -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. -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: -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. -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. -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.) -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. -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. +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. +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: + 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, + 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, + 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.) + 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. +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. +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. + 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. + 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. + 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 + 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. +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. +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. +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. + 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. + 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. +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. + 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 + 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. + 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. + 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 + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/sites/all/modules/contrib/admin/admin_theme/README.txt b/sites/all/modules/contrib/admin/admin_theme/README.txt index 783bbda..e8acffe 100644 --- a/sites/all/modules/contrib/admin/admin_theme/README.txt +++ b/sites/all/modules/contrib/admin/admin_theme/README.txt @@ -1,18 +1,23 @@ -/* $Id: README.txt,v 1.1.2.2.2.1 2008/12/13 09:22:36 davyvandenbremt Exp $ */ Description ----------- -Drupal allows you to define a different theme for administration pages (Administer -> Site configuration -> Administration theme). By default this only applies to pages with a path starting with 'admin' and content editing pages. +Drupal allows you to define a different theme for administration pages +(Administer -> Site configuration -> Administration theme). +By default this only applies to pages with a path starting with 'admin' +and content editing pages. -The Administration theme module adds a few more option to the default configuration page like : +The Administration theme module adds a few more option to +the default configuration page like : - Use administration theme for batch processing - Use administration theme for code reviews - ... -Some of these pages will only appear if they apply to your installation, i.e. you have the module installed which generates these pages. +Some of these pages will only appear if they apply to your installation, +i.e. you have the module installed which generates these pages. -You also have the option to define a custom set of Drupal paths or aliases to apply the administration theme for. +You also have the option to define a custom set of Drupal paths +or aliases to apply the administration theme for. Requirements @@ -41,12 +46,19 @@ Administration theme can be configured at : Developers ---------- -You can add define extra pages where the administration theme should be applied to by implementing the hook_admin_theme_info and hook_admin_theme_check hooks in your modules. -The first one gets all "options" and the second one checks if each of those options should should be applied to a path. Check out admin_theme_admin_theme_info and admin-theme_admin_theme_check for an example implementation. +You can add define extra pages where the administration theme +should be applied to by implementing the hook_admin_theme_info +and hook_admin_theme_check hooks in your modules. +The first one gets all "options" and the second one checks +if each of those options should should be applied to a path. +Check out admin_theme_admin_theme_info and admin-theme_admin_theme_check +for an example implementation. -Author +Authors ------ Davy Van Den Bremt -http://www.davyvandenbremt.be \ No newline at end of file +http://www.davyvandenbremt.be + +Joozer Tohfafarosh \ No newline at end of file diff --git a/sites/all/modules/contrib/admin/admin_theme/admin_theme.api.php b/sites/all/modules/contrib/admin/admin_theme/admin_theme.api.php index 12d1280..f209a4f 100644 --- a/sites/all/modules/contrib/admin/admin_theme/admin_theme.api.php +++ b/sites/all/modules/contrib/admin/admin_theme/admin_theme.api.php @@ -1,23 +1,17 @@ l(t('Administer > Appearance > Administration theme'), 'admin/appearance')); @@ -16,13 +16,14 @@ function admin_theme_install() { * Implementation of hook_uninstall(). */ function admin_theme_uninstall() { - // cleaning up general admin theme variables + // Cleaning up general admin theme variables. variable_del('admin_theme_path'); variable_del('admin_theme_path_disallow'); - - // cleaning up module defined admin theme variables - - // we need to add admin theme's explicitly because it's not in the list of modules anymore + + // Cleaning up module defined admin theme variables. + // We need to add admin theme's explicitly, + // because it's not in the list of modules anymore. + drupal_load('module', 'admin_theme'); $admin_theme_options = admin_theme_admin_theme_info(); $options = array(); foreach ($admin_theme_options as $option => $info) { @@ -30,14 +31,14 @@ function admin_theme_uninstall() { $info['module'] = 'admin_theme'; $options[] = $info; } - - // mergin admin theme's options with other module options - // @fixme does this work if other modules are also uninstalled at the same time? + + // Merging admin theme's options with other module options. + // @fixme Does this work if other modules are also uninstalled simultaneously? $list = array_merge(admin_theme_list(), $options); - - // deleting the module defined variables + + // Deleting the module defined variables. foreach ($list as $info) { $var = admin_theme_variable_name($info['module'], $info['option']); variable_del($var); } -} \ No newline at end of file +} diff --git a/sites/all/modules/contrib/admin/admin_theme/admin_theme.module b/sites/all/modules/contrib/admin/admin_theme/admin_theme.module index 3991ed5..051c36b 100644 --- a/sites/all/modules/contrib/admin/admin_theme/admin_theme.module +++ b/sites/all/modules/contrib/admin/admin_theme/admin_theme.module @@ -1,13 +1,13 @@ 0) { + if ($module_options && count($module_options) > 0) { foreach ($module_options as $option => $info) { $info['option'] = $option; $info['module'] = $module; @@ -54,23 +55,23 @@ function admin_theme_list() { } /** - * Implementation of hook_form_alter(). + * Implements hook_form_alter(). */ function admin_theme_form_system_themes_admin_form_alter(&$form, $form_state) { - // define a fieldset for the page selection + // Define a fieldset for the page selection. $form['admin_theme']['pages'] = array( '#type' => 'fieldset', '#title' => t('Pages'), '#collapsible' => TRUE, - '#description' => t('Choose which pages should be displayed with the administration theme.') + '#description' => t('Choose which pages should be displayed with the administration theme.'), ); - - // add the content editing option to the pages fieldset and change the title + + // Add the content editing option to the pages fieldset and change the title. $form['admin_theme']['pages']['node_admin_theme'] = $form['admin_theme']['node_admin_theme']; $form['admin_theme']['pages']['node_admin_theme']['#title'] = t('Content editing'); unset($form['admin_theme']['node_admin_theme']); - // add all options as checkboxes to the admin theme settings form + // Add all options as checkboxes to the admin theme settings form. $list = admin_theme_list(); foreach ($list as $info) { $var = admin_theme_variable_name($info['module'], $info['option']); @@ -82,7 +83,8 @@ function admin_theme_form_system_themes_admin_form_alter(&$form, $form_state) { ); } - // allow the user to define a set of pages where the admin theme should or should not be applied to + // Allow the user to define a set of pages where the admin theme should, + // or should not be applied to. $form['admin_theme']['pages']['custom'] = array( '#type' => 'fieldset', '#title' => t('Custom'), @@ -112,7 +114,7 @@ function admin_theme_form_system_themes_admin_form_alter(&$form, $form_state) { * Process system_themes_form additions submissions. */ function admin_theme_form_system_themes_form_alter_submit($form, &$form_state) { - // module options + // Module options. $list = admin_theme_list(); foreach ($list as $info) { $var = admin_theme_variable_name($info['module'], $info['option']); @@ -121,36 +123,37 @@ function admin_theme_form_system_themes_form_alter_submit($form, &$form_state) { } } - // custom page options + // Custom page options. variable_set('admin_theme_path', $form_state['values']['admin_theme_path']); variable_set('admin_theme_path_disallow', $form_state['values']['admin_theme_path_disallow']); } /** - * Implementation of hook_custom_theme(). + * Implements hook_custom_theme(). */ function admin_theme_custom_theme() { $admin_theme_disallow = FALSE; $admin_theme = FALSE; - // check if some paths are disallow to get the theme + // Check if some paths are disallow to get the theme. if (trim(variable_get('admin_theme_path_disallow', '')) != '') { - // pages that are defined by their normal path + // Pages that are defined by their normal path. $admin_theme_disallow = drupal_match_path($_GET['q'], variable_get('admin_theme_path_disallow', '')); - // pages that are defined with their alias + // Pages that are defined with their alias. $alias = drupal_get_path_alias($_GET['q']); if ($alias != $_GET['q']) { $admin_theme_disallow = $admin_theme || drupal_match_path($alias, variable_get('admin_theme_path_disallow', '')); } } - // we should not show the admin theme if the user has no access or the path is in the disallow list + // We should not show the admin theme if the user has no access, + // or the path is in the disallow list. if (!user_access('access admin theme') || $admin_theme_disallow) { return; } - // check if an option is enabled and if it results to TRUE + // Check if an option is enabled and if it results to TRUE. $list = admin_theme_list(); foreach ($list as $info) { $var = admin_theme_variable_name($info['module'], $info['option']); @@ -159,12 +162,12 @@ function admin_theme_custom_theme() { } } - // some custom defined pages should get admin theme + // Some custom defined pages should get admin theme. if (trim(variable_get('admin_theme_path', '')) != '') { - // pages that are defined by their normal path + // Pages that are defined by their normal path. $admin_theme = $admin_theme || drupal_match_path($_GET['q'], variable_get('admin_theme_path', '')); - - // pages that are defined with their alias + + // Pages that are defined with their alias. $alias = drupal_get_path_alias($_GET['q']); if ($alias != $_GET['q']) { $admin_theme = $admin_theme || drupal_match_path($alias, variable_get('admin_theme_path', '')); @@ -179,14 +182,14 @@ function admin_theme_custom_theme() { } /** - * Implementation of hook_admin_theme_info(). + * Implements hook_admin_theme_info(). */ function admin_theme_admin_theme_info() { $options = array(); $options['batch'] = array( 'title' => t('Batch processing'), 'description' => t('Use the administration theme when executing batch operations.'), - ); + ); if (module_exists('img_assist')) { $options['img_assist'] = array( 'title' => t('Image assist'), @@ -227,23 +230,29 @@ function admin_theme_admin_theme_info() { } /** - * Implementation of hook_admin_theme_check(). + * Implements hook_admin_theme_check(). */ function admin_theme_admin_theme_check($option = NULL) { switch ($option) { case 'img_assist': return arg(0) == 'img_assist'; + case 'coder': return arg(0) == 'coder'; + case 'devel': return arg(0) == 'devel' || (arg(0) == 'node' && arg(2) == 'devel'); + case 'batch': return arg(0) == 'batch'; + case 'service_attachments': return arg(0) == 'node' && arg(2) == 'service_attachments'; + case 'webform_results': return arg(0) == 'node' && arg(2) == 'webform-results'; + case 'statistics': return (arg(0) == 'node' || arg(0) == 'user') && arg(2) == 'track'; } -} \ No newline at end of file +} diff --git a/sites/all/modules/contrib/admin/admin_theme/translations/admin_theme.pot b/sites/all/modules/contrib/admin/admin_theme/translations/admin_theme.pot deleted file mode 100644 index ff3f8ad..0000000 --- a/sites/all/modules/contrib/admin/admin_theme/translations/admin_theme.pot +++ /dev/null @@ -1,122 +0,0 @@ -# $Id: admin_theme.pot,v 1.1.4.2 2008/12/15 16:08:50 davyvandenbremt Exp $ -# -# LANGUAGE translation of Drupal (general) -# Copyright YEAR NAME -# Generated from files: -# admin_theme.module,v 1.1.2.9 2008/12/13 09:17:01 davyvandenbremt -# admin_theme.install,v 1.1.2.2 2008/12/08 18:37:44 davyvandenbremt -# admin_theme.info,v 1.1 2008/11/14 21:34:23 davyvandenbremt -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: PROJECT VERSION\n" -"POT-Creation-Date: 2008-12-15 16:59+0100\n" -"PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\n" -"Last-Translator: NAME \n" -"Language-Team: LANGUAGE \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" - -#: admin_theme.module:61 -msgid "Theme" -msgstr "" - -#: admin_theme.module:74 -msgid "Pages" -msgstr "" - -#: admin_theme.module:76 -msgid "Choose which pages should be displayed with the administration theme." -msgstr "" - -#: admin_theme.module:81 -msgid "Content editing" -msgstr "" - -#: admin_theme.module:99 -msgid "Custom" -msgstr "" - -#: admin_theme.module:100 -msgid "Enter one page per line as Drupal paths. The '*' character is a wildcard. Example paths are %blog for the blog page and %blog-wildcard for every personal blog. %front is the front page." -msgstr "" - -#: admin_theme.module:107 -msgid "Use administration theme on the following pages." -msgstr "" - -#: admin_theme.module:112 -msgid "Do not use administration theme on the following pages." -msgstr "" - -#: admin_theme.module:113 -msgid "If a path appears here, the administration theme is not shown even if all above options apply." -msgstr "" - -#: admin_theme.module:186 -msgid "Batch processing" -msgstr "" - -#: admin_theme.module:187 -msgid "Use the administration theme when executing batch operations." -msgstr "" - -#: admin_theme.module:191 -msgid "Code reviews" -msgstr "" - -#: admin_theme.module:192 -msgid "Use the administration theme when viewing Coder code reviews." -msgstr "" - -#: admin_theme.module:197 -msgid "Service attachments form on nodes." -msgstr "" - -#: admin_theme.module:198 -msgid "Use the administration theme when viewing service attachments on nodes." -msgstr "" - -#: admin_theme.module:203 -msgid "Webform submissions." -msgstr "" - -#: admin_theme.module:204 -msgid "Use the administration theme when viewing webform submissions." -msgstr "" - -#: admin_theme.module:209 -msgid "Pages defined by the statistics module." -msgstr "" - -#: admin_theme.module:210 -msgid "Use the administration theme when viewing pages of the statistics module." -msgstr "" - -#: admin_theme.module:13 -msgid "access admin theme" -msgstr "" - -#: admin_theme.module:0 -msgid "admin_theme" -msgstr "" - -#: admin_theme.install:8 -msgid "Administer > Site configuration > Administration theme" -msgstr "" - -#: admin_theme.install:9 -msgid "Administration theme module settings are available under !link" -msgstr "" - -#: admin_theme.info:0 -msgid "Administration theme" -msgstr "" - -#: admin_theme.info:0 -msgid "Enable the administration theme on more pages then possible with Drupal's default administration page." -msgstr "" - diff --git a/sites/all/modules/contrib/admin/admin_theme/translations/nl.po b/sites/all/modules/contrib/admin/admin_theme/translations/nl.po deleted file mode 100644 index 0ee3aae..0000000 --- a/sites/all/modules/contrib/admin/admin_theme/translations/nl.po +++ /dev/null @@ -1,122 +0,0 @@ -# $Id: nl.po,v 1.2.2.2.4.1 2008/12/15 16:08:50 davyvandenbremt Exp $ -# -# Dutch translation of Drupal (general) -# Copyright YEAR NAME -# Generated from files: -# admin_theme.module,v 1.1.2.9 2008/12/13 09:17:01 davyvandenbremt -# admin_theme.install,v 1.1.2.2 2008/12/08 18:37:44 davyvandenbremt -# admin_theme.info,v 1.1 2008/11/14 21:34:23 davyvandenbremt -# -msgid "" -msgstr "" -"Project-Id-Version: Administration Theme\n" -"POT-Creation-Date: 2008-11-29 10:18+0100\n" -"PO-Revision-Date: 2008-11-29 10:27+0100\n" -"Last-Translator: Davy Van Den Bremt \n" -"Language-Team: Davy Van Den Bremt \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Poedit-Language: Dutch\n" - -#: admin_theme.module:61 -msgid "Theme" -msgstr "Template" - -#: admin_theme.module:74 -msgid "Pages" -msgstr "Pagina's" - -#: admin_theme.module:76 -msgid "Choose which pages should be displayed with the administration theme." -msgstr "Kies welke pagina's met de beheertemplate getoond moeten worden." - -#: admin_theme.module:81 -msgid "Content editing" -msgstr "Bewerken van inhoud" - -#: admin_theme.module:99 -msgid "Custom" -msgstr "Specifiek" - -#: admin_theme.module:100 -msgid "Enter one page per line as Drupal paths. The '*' character is a wildcard. Example paths are %blog for the blog page and %blog-wildcard for every personal blog. %front is the front page." -msgstr "Geef één pagina per regel op, als Drupal-paden. Het '*'-teken is een jokerteken. Voorbeeldpaden zijn '%blog' voor de blog-pagina en '%blog-wildcard' voor elke persoonlijke blog. '%front' is de voorpagina." - -#: admin_theme.module:107 -msgid "Use administration theme on the following pages." -msgstr "Gebruik de beheertemplate voor de volgende pagina's." - -#: admin_theme.module:112 -msgid "Do not use administration theme on the following pages." -msgstr "Gebruik de beheertemplate niet voor de volgende pagina's." - -#: admin_theme.module:113 -msgid "If a path appears here, the administration theme is not shown even if all above options apply." -msgstr "Wanneer een pad hier vermeld wordt, word de beheertemplate niet getoond, zelfs als bovenstaande opties van toepassing zijn." - -#: admin_theme.module:186 -msgid "Batch processing" -msgstr "Batch verwerking" - -#: admin_theme.module:187 -msgid "Use the administration theme when executing batch operations." -msgstr "Gebruik de beheertemplate tijdens het verwerken van batch handelingen." - -#: admin_theme.module:191 -msgid "Code reviews" -msgstr "Code revisies" - -#: admin_theme.module:192 -msgid "Use the administration theme when viewing Coder code reviews." -msgstr "Gebruik de beheertemplate tijdens het bekijken van Coder's code revisies." - -#: admin_theme.module:197 -msgid "Service attachments form on nodes." -msgstr "Service attachments formulier bij nodes." - -#: admin_theme.module:198 -msgid "Use the administration theme when viewing service attachments on nodes." -msgstr "Gebruik de beheertemplate tijdens het bekijken van het service attachments formulier bij nodes." - -#: admin_theme.module:203 -msgid "Webform submissions." -msgstr "Webformulier inzendingen." - -#: admin_theme.module:204 -msgid "Use the administration theme when viewing webform submissions." -msgstr "Gebruik de beheertemplate tijdens het bekijken van webformulier inzendingen." - -#: admin_theme.module:209 -msgid "Pages defined by the statistics module." -msgstr "Pagina's van de statistics module" - -#: admin_theme.module:210 -msgid "Use the administration theme when viewing pages of the statistics module." -msgstr "Gebruik de beheertemplate tijdens het bekijken van pagina's van de statistics module." - -#: admin_theme.module:13 -msgid "access admin theme" -msgstr "toegang tot beheertemplate" - -#: admin_theme.module:0 -msgid "admin_theme" -msgstr "admin_theme" - -#: admin_theme.install:8 -msgid "Administer > Site configuration > Administration theme" -msgstr "Beheren > Site-instellingen > Beheertemplate" - -#: admin_theme.install:9 -msgid "Administration theme module settings are available under !link" -msgstr "Instellingen voor de Administration theme module zijn beschikbaar onder !link" - -#: admin_theme.info:0 -msgid "Administration theme" -msgstr "Beheertemplate" - -#: admin_theme.info:0 -msgid "Enable the administration theme on more pages then possible with Drupal's default administration page." -msgstr "Gebruik de beheertemplate op meer pagina's dan instelbaar met Drupal's standaard beheer pagina." - diff --git a/sites/all/modules/contrib/admin/backup_migrate/CHANGELOG.txt b/sites/all/modules/contrib/admin/backup_migrate/CHANGELOG.txt new file mode 100644 index 0000000..0bf88c7 --- /dev/null +++ b/sites/all/modules/contrib/admin/backup_migrate/CHANGELOG.txt @@ -0,0 +1,310 @@ +Backup and Migrate 7.x-3.9, 2020-07-15 +-------------------------------------- +#3158379 by DamienMcKenna, monnerat: Fixed compatibility with PHP 5.4. + + +Backup and Migrate 7.x-3.8, 2020-07-10 +-------------------------------------- +#3119038 by solideogloria, gisle: Spelling typo in update scripts. +#3143362 by DamienMcKenna, solideogloria: Undefined variable $machine_name in + install file. +#3143250 by DamienMcKenna: Use utf8mb4 for MySQL files. +#3154166 by DamienMcKenna: Remove use of backup_migrate_include() entirely. +#3129399 by solideogloria, DamienMcKenna: Missing ctools_export_form parameter. +#3158378 by DamienMcKenna, ron_s: Adjust default performance settings. +#3158379 by DamienMcKenna: Coding standards improvements. + + +Backup and Migrate 7.x-3.7, 2020-02-28 +-------------------------------------- +#3032453 by DamienMcKenna: Ignore Advanced Aggregator CSS & JS directories. +#3026187 by DamienMcKenna: Configurations don't load correctly when upgrading + from 7.x-2.x to 3.x, clear caches. +#2606586 by Darren Oh, dmargetts, joelpittet, DamienMcKenna, solideogloria: Fix + smart delete not saving files for all periods. +#3026186 by DamienMcKenna: Menus not rebuilt after upgrading from 7.x-2.x. +#2507495 by brankoc, DamienMcKenna, nicorac, lorisbel, crantok, jakeru, + NickWilde, DD 85, rothlive, Oleg Ko, mgstables, szczesuil, Web.Pergamonteam, + Lemontonix, Tranko, aotus, knalstaaf, drugan, lesliewagner1165, Pascal.s, + HansKuiters, mato, lgough, Frosty29, sdowney2002, dhalbert, toomanypets, ckng, + havanablackcu1, myDrupal2014_846824658246, felixmf: Warning: mail(): Multiple + or malformed newlines found in additional_header in mime_mail->send(). +#2135813 by RickJ, rlhawk: Support for the Encrypt module. +#3033583 by BrankoC: Add CTools as a test dependency. +#2946311 by DamienMcKenna, k.elizabeth, couturier, jackalope, nelslynn, + rrirower, bmango, Cyberflyer, sharonknieper: Too many connections in + backup_migrate_source_db->_get_db_connection(). +#3023804 by DamienMcKenna, solideogloria, adam1, jazzitup, MaxMendez, jprj, + Louis Delacretaz, eloiv, oliverpolden, drupal_jon, mato, VirtualMitra: Error: + Call to undefined function _backup_migrate_temp_files_delete(). +#2663066 by BrankoC, DamienMcKenna, bsains, izmeez: Profile Filter field - + hitting 64Kb limit. +#3023414 by BrankoC, DamienMcKenna, wylbur: Provide settings page for the hidden + variables. +#1042672 by fietserwin, BrankoC: hook_help: do not translate texts if not + necessary. +#3060997 by solideogloria, BrankoC: Improve Performance of Schedule Calculation. +#3064783 by BrankoC, DamienMcKenna: Uninstalling this module leaves variable + backup_migrate_schedule_last_run_* intact. +#3051821 by DamienMcKenna, BrankoC, solideogloria, wylbur: Remove NodeSquirrel + integration. +#3033583 by BrankoC, DamienMcKenna, m.stenta, RickJ, wylbur: CTools exportables + code no longer loading. +#2507495 by BrankoC, DamienMcKenna: Update script to disable destinations + affected by the email delivery change. +#3046235 by joelpittet: Slow query 'show table status' on menu rebuild. +#3102427 by izmeez: Readme.txt minor typo and formatting fixes. +#3102506 by DamienMcKenna, BrankoC: Verify destinations work before listing them + in the Quick Backup form; save the file system form before beginning backup + tests to ensure the private file system is configured correctly. +#3102218 by DamienMcKenna, BrankoC, jacob.embree, izmeez, devad, joelpittet, + fietserwin, wylbur: Update 7310 Disable e-mail destinations. Emits syntax + error for MySQL and MariaDB. +#3027709 by BrankoC, DamienMcKenna, ultrabob: "A non-numeric value encountered" + in backup_migrate_to_bytes(). +#3116733 by DamienMcKenna, fietserwin: PDOException: SQLSTATE[42S02]: Base table + or view not found: 1146 Table 'variable' doesn't exist. +#2885094 by pdcarto, avergara, couturier: CLI option dumps the ENTIRE database, + ignores 'nodata_tables' setting. + + +Backup and Migrate 7.x-3.6, 2018-12-15 +-------------------------------------- +#2293601 by KimNyholm, fastturtle: Errors shown because directory handles are + not closed when no longer needed. +#2937023 by DamienMcKenna: Add a test for the basic UI functionality. +#2945704 by DamienMcKenna: Write tests for each backup filename option. +#2939277 by DamienMcKenna, baronmunchowsen: File mode change on + sources.archivesource.inc. +#2935403 by DamienMcKenna, jacob.embree: Improve coding standards compliance. +#2962822 by laryn: Spelling mistake: "frequecy". +#2917959 by DamienMcKenna: Updated backup_migrate_update_7305() to fix schema + problems. +#2996191 by mwnciau, DamienMcKenna: Special characters in database password + causes PHP notifications. +#2949211 by gturnbull, DamienMcKenna, Alex Bukach, Eli.Stone, joelpittet: + Optional memory limit setting to work around out-of-memory errors during + backups. +#2494757 by pbuyle, DamienMcKenna, Ives, Calystod: Allow running a scheduled + backup with Drush. +#2404113 by DamienMcKenna, John Cook, danharper, salmino: Different host for + AWS type server. +#2829492 by axel.rutz, RickJ, DamienMcKenna, devad, Kris77, pietrocap, + couturier: Big file backup breaks with "MySQL server has gone away". +#3001702 by DamienMcKenna: Code cleanup on 7.x-3.x branch. +#2831470 by snehal.addweb, bfodeke, jigish.addweb: Delete query in + hook_uninstall() can potentially remove unintended variables. +#3004759 by Manthan.addweb, echoz: Notice: Undefined index: exclude_filepaths... + visiting Advanced Backup tab. +#3013355 by jacob.embree: Syntax error in backup_migrate.js. +#2415421 by ron_s, coredumperror, blake.thompson, John Bickar: Backup and + Migrate module and private file system. +#3002975 by DamienMcKenna: Improve logging around the cron and backup processes. +#2747197 by RickJ: Network errors writing to Nodesquirrel handled badly. +#3014594 by DamienMcKenna: backup_migrate_uninstall() doesn't delete all. + variables; renamed "backup_migrate_backup_memory_limit" variable to + "backup_migrate_memory_limit". +#3014601 by DamienMcKenna: Re-enable all tests. +#3014143 by DamienMcKenna: Add master switch to turn off hook_cron. +#3014603 by DamienMcKenna: Add a CHANGELOG.txt file. +#3014596 by DamienMcKenna: Require PHP 5.4. +#2943719 by DamienMcKenna: Reduce memory usage during MySQL database generation. +#2338841 by catch, dasha_v, DamienMcKenna: Incorrect translated string + + menu_rebuild() performance issue. +#1104012 by DamienMcKenna, axel.rutz, EmanueleQuinto, gisle, jacob.embree, Pere + Orga, frenkx: On restore, provide option to drop all tables. +#2891272 by DamienMcKenna, webservant316: phpsnif 7.1 says Method name + \backup_migrate_destination_nodesquirrel::__xmlrpc\" is discouraged; PHP has + reserved all method names with a double underscore prefix for future use. +#3014597 by DamienMcKenna: Document the advanced backup options. +#2382541 by sano, DamienMcKenna, couturier, dman, modulist: Updated + instructions for installing the S3 library. +#3016278 by DamienMcKenna: Some debug messages aren't hidden behind + backup_migrate_verbose. +#3016282 by DamienMcKenna: "Cannot add field + backup_migrate_profiles.machine_name: field already exists" error. +#3017042 by DamienMcKenna, freelylw, gisle: Settings form field dependency + broken. +#3018002 by DamienMcKenna: Improve the module's documentation. +By DamienMcKenna: "server" not "sever" :-) +#3018853 by RickJ: Temporary files not deleted. +#2735337 by RickJ: Settings objects only offer Revert, not Delete. + + +Backup and Migrate 7.x-3.5, 2018-02-05 +-------------------------------------- +#2941981 by DamienMcKenna, Rick J, camhoward: Backup files being overwritten + because of a change to the append_timestamp setting. +#2940451 by Darren Oh: Unlimited max_execution_time treated as 0 seconds. + + +Backup and Migrate 7.x-3.4, 2018-01-24 +-------------------------------------- +By DamienMcKenna, pere-orga, ikit-claw, cashwilliams: Make all permissions + restricted. +#1058820 by kbasarab, sobi3ch, gbirch, avatxus: Add option to overwrite file. +#2498191 by DamienMcKenna: Unable to backup to NodeSquirrel using PHP 5.6.9 + (on Windows) due to changes in SSL validation rules. +#2742855 by hanoii: Caching profile objects led to unwanted behaviors because of + stale data. +#2703351 by DamienMcKenna, ultrabob: Redundant methods in + backup_migrate_location_remote. +#2728345 by Rick J: Nodesquirrel destination class doesn't return its name. +#2728331 by Rick J: Incorrect paging count in saved backup list. +#2498179 by junaidpv: Use drupal_mail() for system notifications. +#2920311 by DamienMcKenna: Check backup directory permissions / web + accessibility. +#2912459 by DamienMcKenna: Add an initial test. +#2880434 by DamienMcKenna, benqwerty: Minor changes to drush help output. +#2914644 by DamienMcKenna, jacob.embree: List all files which contain classes in + the info file so other modules can use the classes. +#2663928 by DamienMcKenna, fietserwin: public://languages should also be + excluded by default. +#2388347 by DamienMcKenna: Nodequirrel destination class doesn't define $errno, + $errstr but assumes they're available. +#2899586 by juankvillegas, DamienMcKenna: Ensure the page variable is numeric in + backup_migrate_nodesquirrel_status_form(). + + +Backup and Migrate 7.x-3.3, 2017-10-18 +-------------------------------------- +#2290707 by DamienMcKenna, mitsuroseba, Darren Oh: Exclude the data from the + following tables - does not work. +#2830554 by axel.rutz: Support Ultimate Cron. +#2728349 by Rick J: Disabled schedules still run under Elysia cron. + + +Backup and Migrate 7.x-3.2, 2017-09-27 +-------------------------------------- +#2702229 by DamienMcKenna: Automatically add all cache tables to the 'nodata' + list, exclude simpletest tables. +#2618516 by rocketeerbkw: Fatal error when restoring "Entire Site". +#2839264 by wizonesolutions: Error: [] operator not supported for strings in + backup_migrate_crud_ui_list_all(). +#2633304 by adamelleston: 'clone' is a reserved keyword introduced in PHP + version 5.0 and cannot be invoked as a function. +#2547065 by snehi: Description for filesource location is inaccurate. +#2672478 by serverofworld, lklimek: Wrong destination when running multiple + schedules using the same profile. +#1542274 by dale42, LGLC, AjitS: Restore fails with foreign key constraint error + (due to incorrect comment detection). +#2623598 by SylvainM, joelstein: Deprecated: Methods with the same name as their + class will not be constructors in a future version of PHP. +#2513720 by rrfegade: Code Spell errors. +By ronan: Cleaned up use of manage url generator. +#2495681 by DamienMcKenna: Secure all endpoint connections to NodeSquirrel + destination service. +#2495677 by DamienMcKenna: Inconsistent use of https for manage.nodesquirrel.com + hostname. +#2277383 by hswong3i: Fix schema mismatch after upgrade 7.x-3.x. + + +Backup and Migrate 7.x-3.1, 2015-05-11 +-------------------------------------- +By ronan: Added pantheon notice and free notice to NodeSquirrel page. +#2347637: Add a Note to Backup doesn't work with Jquery > 1.7. +By ronan: Set Nodesquirrel schedule to use smart delete by default. +#2477503: Error returned, but exit status is 0. +#2369461: Scheduled Backups stopped working after 7.x-3.0?. +#2390057: Site doesn't come out of maintenance mode when backing up entire site. +#2455265: Update Version Number in README.TXT. +By ronan: Updated NS messaging slightly. +#2393615: Compression not running (only when backing up entire site). +#2324949: Update #7303 Failing. +#2421183: Stream URI - Not ignored (Recognized?) during backups. +#2378739: "Exclude the data from the following tables" not respected when "Use + cli commands" is checked. +#2307655 Fixed adding of full system path to site archive. +#2290707 by marvoey Advanced Backup link on quick backup page goes to wrong + place. +#2290707 Exclude the data from the following tables - does not work. +By ronan: Moved run time check before scheduled backup attempt to fix too- + frequent schedule issue. +#2280743: Files restore duplicating directory structure + (sites/default/files/sites/default/files). +#2276663 Permissions - Access backup files. +#2283701 by alarcombe. Restore not possible from archive. +#2286835 by Fernando Vesga. Advanced backup page link error. +#2287157 by topsitemakers. Links to "Create new destination" and "Create new + schedule" are invalid. +#2287239 by topsitemakers. Minor typo in an error message. +#2278865 by topsitemakers Undefined index: files in + theme_backup_migrate_file_list. + + +Backup and Migrate 7.x-3.0, 2014-05-27 +-------------------------------------- +By ronan: Fixed comment on install hook. +#2266381 Warning: Invalid argument supplied for foreach() in + backup_migrate_schedules_cron() on line 48. +#1001654 Fixed ctools exportables. +By ronan: Added nodesquirrel scheduling back. Fixed issue with restore from + previous radios. Added function to retrieve the most recent backup from the + given destination. This may be used in a drush command. +By ronan: Removed link to help text when help module is off. +By ronan: Removed confusing 'Quick Schedule' tab. +#2225335 Drush commands not working. +By ronan: Increased machine_name length in ui for compatibility with migrated + items. +By ronan: Fixed a couple of php errors. +By ronan: Minor wording tweaks. +By ronan: Added better status for NodeSquirrel. +By ronan: Removed dpm. +By ronan: Added dependency to offline message on restore. +By ronan: Removed stray console.log from the js. +By ronan: Added most recent saved backups to the restore tab. +By ronan: Ported MySQL views support. +By ronan: Fixed some issues saving profiles. +By ronan: Added a @TODO to remind me to fix saving on the advance backup page. +#1974740 allow backup of codebase & files to skip unreadable files. +By ronan: Merge branch 'refs/heads/NodeSquirrel-Language' into 7.x-3.x. +By ronan: Fixed up some language and links to NodeSquirrel. +By ronan: Cleaned up directory handling to support more directory structures. +By ronan: Added some description language for NodeSquirrel. +#2166813 Files backup fails when PEAR is not installed. +#2155381 by JulienD Remove useless files[] directive from .info files. +#1947206 by Les Lim. "No data" tables are exported even when they're in the + "Exclude the following tables altogether" list. +#2031393 A debugging function call left behind. +#2031777 Wrong function signature on form callback. +#2065573 wrong default file token. +#2039951 settings page gone for schedules. +#2009392 Settings don't save for file or entire site sources. +#1998788 missing i in NodeSquirrel. +By ronan: Moved NodeSquirrel tab to match the 2.x branch. +By ronan: Some d7 style cleanup. +By ronan: Fixed nodesquirrel destination with new id structure. +By ronan: Added checkplain to description on listing. +By ronan: Fixed an issue with multiselect js invading other pages. +#1564408 Gzip backups are compressed twice when downloaded. +#1991686 thx morningtime. Undefined property: backup_migrate_schedule::$last_run +By ronan: Switch to hourly default to align with smart delete default. +#839254 Switched to using drupal_reapath to allow for stream wrapper schemas. +#839254 Checking absolute paths within the docroot. +#839254 check for access restriction ignores custom destination path. +#1503202 Suhosin refuses sending mail because of to many new lines in mail + header (possible attacker). +#1529174 Fatal Error when database name is longer than 21 characters. +By ronan: Fixed 'Restore again' link. +#1974720 error message on attempting to restore manual backup. +#1974744 warning on creation of new settings profile. +By ronan: Fixed inconsistent use of 'source' and 'destination'. +By ronan: Changed destination_id to machine_name to fix issue with default file + destinations. +By ronan: Fixed error noise caused by ftp not connecting. +By ronan: Fixed error on file delete form. +By ronan: Fixed fatal error in metadata util when a backup fails. +#1968210 error message on attempting backup of entire site. +By ronan: Improved error reporting when a non-existant file (such as a broken + symlink) cannot be backed up. +#1968196 Error message on install in php 5.4. +By ronan: Fixed checkboxes. +By ronan: Fixed fatal error with token replace. +By ronan: Created full upgrade path from 2.x. Fixed source settings. +By ronan: Fixed db scheme issue. Fixed file source realpath issue. +By ronan: Switched type to subtype for ctools compatibility. +By ronan: Added machine_name js and ctools exportables. +By ronan: Fixed source specific settings in backup. +By ronan: Fixed issue with edit screen for source settings in profiles. +By ronan: Fixed export. +By ronan: Cleaned up more fatal errors. Better handling of translatable titles. diff --git a/sites/all/modules/contrib/admin/backup_migrate/README.txt b/sites/all/modules/contrib/admin/backup_migrate/README.txt index 7ecc15d..f908f37 100644 --- a/sites/all/modules/contrib/admin/backup_migrate/README.txt +++ b/sites/all/modules/contrib/admin/backup_migrate/README.txt @@ -1,94 +1,216 @@ +Backup and Migrate +------------------ +This module makes the task of backing up a site's Drupal database, code and +all uploaded files and of migrating data from one Drupal install to another +easier. +Database backup files are a list of SQL statements which can be executed with a +tool such as phpMyAdmin or the command-line mysql client. File and code backup +files are tarballs which can be restored by extracting them to the desired +directory. + + +Installation ------------------------------------------------------------------------------- -Backup and Migrate 2 for Drupal 7.x - by Ronan Dowling, Gorton Studios - ronan (at) gortonstudios (dot) com -------------------------------------------------------------------------------- - -DESCRIPTION: -This module makes the task of backing up your Drupal database and migrating data -from one Drupal install to another easier. It provides a function to backup the -entire database to file or download, and to restore from a previous backup. You -can also schedule the backup operation. Compression of backup files is also -supported. - -There are options to exclude the data from certain tables (such as cache or -search index tables) to increase efficiency by ignoring data that does not need -to be backed up or migrated. - -The backup files are a list of SQL statements which can be executed with a tool -such as phpMyAdmin or the command-line mysql client. - -------------------------------------------------------------------------------- - -INSTALLATION: -* Put the module in your Drupal modules directory and enable it in - admin/modules. -* Go to admin/people/permissions and grant permission to any roles that need to be - able to backup or restore the database. -* Configure and use the module at admin/config/system/backup_migrate +* Put the module in the Drupal modules directory and enable it via + admin/modules. +* Go to admin/people/permissions and grant permission to any roles that need to + be able to backup or restore the database. +* Configure and use the module at admin/config/system/backup_migrate. OPTIONAL: +* To drop all tables before import, expand "Advanced options" panel under the + "Restore" and "Saved backups" tabs and tick the option. * Enable token.module to allow token replacement in backup file names. * To Backup to Amazon S3: - - Download the S3 library from http://undesigned.org.za/2007/10/22/amazon-s3-php-class - and place the file 'S3.php' in the includes directory in this module. - The stable version (0.4.0 – 20th Jul 2009) works best with Backup and Migrate. + - Download the most recent version from: + https://github.com/tpyo/amazon-s3-php-class + - Or clone it with command: + git clone https://github.com/tpyo/amazon-s3-php-class.git s3-php5-curl + - Rename the unzipped folder to s3-php5-curl -LIGHTTPD USERS: -Add the following code to your lighttp.conf to secure your backup directories: +The most recent version of the library known to work is 0.5.1. + + +Advanced settings +------------------------------------------------------------------------------- +Several advanced options are available from the Advanced Settings page: +* admin/config/system/backup_migrate/settings-advanced + +These settings should be handled with care, it is recommended to leave them at +their defaults unless there is a specific need to modify them. + + +Additional requirements for LigHTTPd +------------------------------------------------------------------------------- +Add the following code to the lighttp.conf to secure the backup directories: $HTTP["url"] =~ "^/sites/default/files/backup_migrate/" { - url.access-deny = ( "" ) + url.access-deny = ( "" ) } -You may need to adjust the path to reflect the actual path to the files. +It may be necessary to adjust the path to reflect the actual path to the files. -IIS 7 USERS: -Add the following code to your web.config code to secure your backup directories: + +Additional requirements for IIS 7 +------------------------------------------------------------------------------- +Add the following code to the web.config code to secure the backup +directories: -You may need to adjust the path to reflect the actual path to the files. +It may also be necessary to adjust the path to reflect the actual path to the +files. + +VERY IMPORTANT SECURITY NOTE ------------------------------------------------------------------------------- - -VERY IMPORTANT SECURITY NOTE: -Backup files may contain sensitive data and by default, are saved to your web +Backup files may contain sensitive data and, by default, are saved to the web server in a directory normally accessible by the public. This could lead to a very serious security vulnerability. Backup and Migrate attempts to protect backup files using a .htaccess file, but this is not guaranteed to work on all -environments (and is guaranteed to fail on web servers that are not apache). You -should test to see if your backup files are publicly accessible, and if in doubt -do not save backups to the server, or use the destinations feature to save to a -folder outside of your webroot. +environments (and is guaranteed to fail on web servers that are not Apache). It +is imperative to test to see if the site's backup files are publicly +accessible, and if in doubt do not save backups to the server, or use the +destinations feature to save to a folder outside of the site's webroot. -OTHER WARNINGS: -A failed restore can destroy your database and therefore your entire Drupal + +Other warnings +------------------------------------------------------------------------------- +A failed restore can destroy the database and therefore the entire Drupal installation. ALWAYS TEST BACKUP FILES ON A TEST ENVIRONMENT FIRST. If in doubt do not use this module. -This module has only been tested with MySQL and does not work with any other dbms. -If you have experiences with Postgres or any other dbms and are willing to help -test and modify the module to work with it, please contact the developer at -ronan (at) gortonstudios (dot) com. +This module has only been tested with MySQL and does not work with any other +dbms. Assistance in adding support for other database systems would be +appreciated, see the issue queue for further details. -Make sure your php timeout is set high enough to complete a backup or restore +Make sure the PHP timeout is set high enough to complete a backup or restore operation. Larger databases require more time. Also, while the module attempts to keep memory needs to a minimum, a backup or restore will require -significantly more memory then most Drupal operations. +significantly more memory than most Drupal operations. -If your backup file contains the 'sessions' table all other users will be logged -out after you run a restore. To avoid this, exclude the sessions table when -creating your backups. Be aware though that you will need to recreate the -sessions table if you use this backup on an empty database. +If the backup file contains the 'sessions' table all other users will be logged +out after running a restore. To avoid this, exclude the sessions table when +creating the backups. Be aware though that this table is still required to run +Drupal, so it will need to be recreated if the backup is restored onto an empty +database. Do not change the file extension of backup files or the restore function will be -unable to determine the compression type the file and will not function +unable to determine the compression type of the file and will not function correctly. -IF A RESTORE FAILS: -Don't panic, the restore file should work with phpMyAdmin's import function, or -with the mysql command line tool. If it does not, then it is likely corrupt; you -may panic now. MAKE SURE THAT THIS MODULE IS NOT YOUR ONLY FORM OF BACKUP. +The module's permissions should only be given to trusted users due to the +inherent security vulnerabilities in allowing people access to a site's database +and/or files backups. + +If a restore fails ------------------------------------------------------------------------------- +Don't panic! +The restore file should still work with phpMyAdmin's import function or with +the mysql command line tool. + +If the backup does not restore using either a graphical tool or the command line +tools, then it is likely corrupt; you may panic now. + +MAKE SURE THAT THIS MODULE IS NOT THE ONLY FORM OF BACKUP. + + +Known problems and workarounds +------------------------------------------------------------------------------- +* If backups fail due to an out-of-memory, try adjusting the memory limit using + the "backup_migrate_memory_limit" variable by adding one of these lines + to the site's settings.php file: + + // Backup & Migrate: Use 512MB when generating backups. + $conf['backup_migrate_memory_limit'] = '512M'; + + // Backup & Migrate: Use 1GB when generating backups. + $conf['backup_migrate_memory_limit'] = '1G'; + +* If backups fail due to a PHP timeout error, especially an error saying "MySQL + server has gone away", use the "backup_migrate_backup_max_time" variable to + adjust the timeout. Before doing this, check to see what PHP's + "max_execution_time" is set to, then set the "backup_migrate_backup_max_time" + variable to a higher number, e.g. if max_execution_time is 180 (seconds) try + setting backup_migrate_backup_max_time to 240 seconds / 4 minutes, or 300 + seconds / 5 minutes + + // Backup & Migrate: Adjust the PHP timeout to 5 minutes / 300 seconds. + $conf['backup_migrate_backup_max_time'] = 300; + +* A variable has been added which may help with problems. Setting the variable + 'backup_migrate_verbose' to TRUE will make the module log additional messages + to watchdog as the module performs certain actions. + + // Backup & Migrate: Log extra messages as the module is working. + $conf['backup_migrate_verbose'] = TRUE; + +* It can be frustrating working from a production database backup on non-prod + servers as schduled backups will automatically run via cron the same as they + run on production. The custom cron tasks may be disabled using the + "backup_migrate_disable_cron" variable. Note: this doesn't prevent people + from manually running backups via the UI or from the Drush commands, so it is + safe to hardcode to TRUE on all site instances and then hardcode to FALSE on + production environments. + + // Backup & Migrate: Don't run backups via cron. + $conf['backup_migrate_disable_cron'] = TRUE; + +* There are three different variables that control how MySQL data is processed. + Should a site have problems with memory limits, it is worth testing these to + see which ones work the best. + + - backup_migrate_data_rows_per_query + Controls how many records are loaded from the database at once. Defaults to + "1000", i.e. 1,000 rows. Note that setting this to a high number can cause + problems when exporting large data sets, e.g. cache tables can have huge + volumes of data per record. + + // Backup & Migrate: Load 10,000 rows at once. + $conf['backup_migrate_data_rows_per_query'] = 10000; + + - backup_migrate_data_rows_per_line + Controls how many records are included in a single INSERT statement. + Defaults to "30", i.e. 30 records. + + // Backup & Migrate: Combine no more than five records in a single row. + $conf['backup_migrate_data_rows_per_line'] = 5; + + - backup_migrate_data_bytes_per_line + Controls how much data will be inserted at once using a single INSERT + statement. This works with the "backup_migrate_data_rows_per_line" variable + to ensure that each INSERT statement doesn't end up being too large. + Defaults to "2000", i.e. 2,000 bytes. + + // Backup & Migrate: Limit the output to 1000 bytes at a time. + $conf['backup_migrate_data_bytes_per_line'] = 1000; + + +Development notes +-------------------------------------------------------------------------------- +It is worth noting that some of the tests will fail when ran against nginx, +which is the default web server for some local development systems. As a result, +it is recommended to run tests on a server that uses Apache HTTPd Server instead +of nginx. + + +Credits / contact +-------------------------------------------------------------------------------- +Currently maintained by Alex Andrascu [1], Damien McKenna [2] and +Daniel Pickering [3]. All original development up through 2015 was by +Ronan Dowling [4] with help by Drew Gorton [5]. + +The best way to contact the authors is to submit an issue, be it a support +request, a feature request or a bug report, in the project issue queue: + https://www.drupal.org/project/issues/backup_migrate + + +References +-------------------------------------------------------------------------------- +1: https://www.drupal.org/u/alex-andrascu +2: https://www.drupal.org/u/damienmckenna +3: https://www.drupal.org/u/ikit-claw +4: https://www.drupal.org/u/ronan +5: https://www.drupal.org/u/dgorton diff --git a/sites/all/modules/contrib/admin/backup_migrate/backup_migrate.advanced_settings.inc b/sites/all/modules/contrib/admin/backup_migrate/backup_migrate.advanced_settings.inc new file mode 100644 index 0000000..8bf6b56 --- /dev/null +++ b/sites/all/modules/contrib/admin/backup_migrate/backup_migrate.advanced_settings.inc @@ -0,0 +1,188 @@ + t('These settings should be handled with care, it is recommended to leave them at their defaults unless there is a specific need to modify them.'), + '#prefix' => '

', + '#suffix' => '

', + ); + $form['backup_migrate_memory_limit'] = array( + '#type' => 'textfield', + '#title' => t('Memory Limit'), + '#default_value' => variable_get('backup_migrate_memory_limit', BACKUP_MIGRATE_MEMORY_LIMIT), + '#description' => t('If backups fail due to an out-of-memory, try adjusting the memory limit using a php.ini style value, for example 512M for 512 megabytes or 1G for 1 gigabyte. Default: !default.', array('!default' => '' . check_plain(BACKUP_MIGRATE_MEMORY_LIMIT) . '')), + '#required' => FALSE, + '#element_validate' => array('backup_migrate_memory_limit_validate'), + ); + $form['backup_migrate_backup_max_time'] = array( + '#type' => 'textfield', + '#title' => t('Time Limit'), + '#default_value' => variable_get('backup_migrate_backup_max_time', BACKUP_MIGRATE_BACKUP_MAX_TIME), + '#description' => t('If backups fail due to a PHP timeout error, especially an error saying "MySQL server has gone away", increase this setting. Before doing this, check to see what PHP\'s "max_execution_time" is set to, then set this field to a higher number, e.g. if max_execution_time is 180 (seconds) try setting Time Limit to 240 seconds (4 minutes), or 300 seconds (5 minutes). Default: !default.', array('!default' => '' . check_plain(BACKUP_MIGRATE_BACKUP_MAX_TIME) . '')), + '#required' => FALSE, + '#element_validate' => array('element_validate_integer_positive'), + ); + $form['backup_migrate_verbose'] = array( + '#type' => 'checkbox', + '#title' => t('Verbose Output'), + '#default_value' => variable_get('backup_migrate_verbose', BACKUP_MIGRATE_VERBOSE), + '#description' => t('Enable this to make the module log additional messages to watchdog as the module performs certain actions. Default: !default.', array('!default' => (BACKUP_MIGRATE_VERBOSE ? 'on' : 'off'))), + '#required' => FALSE, + ); + $form['backup_migrate_disable_cron'] = array( + '#type' => 'checkbox', + '#title' => t('Disable Cron'), + '#default_value' => variable_get('backup_migrate_disable_cron', BACKUP_MIGRATE_DISABLE_CRON), + '#description' => t('It can be frustrating working from a production database backup on non-prod servers as schduled backups will automatically run via cron the same as they run on production. The custom cron tasks may be disabled using this checkbox. Note: this doesn\'t prevent people from manually running backups via the UI or from the Drush commands, so it is safe to enable on all site instances and then disable on production environments. Default: !default.', array('!default' => (BACKUP_MIGRATE_DISABLE_CRON ? 'on' : 'off'))), + '#required' => FALSE, + ); + $form['backup_migrate_data_rows_per_query'] = array( + '#type' => 'textfield', + '#title' => t('Rows Per Query'), + '#default_value' => variable_get('backup_migrate_data_rows_per_query', BACKUP_MIGRATE_DATA_ROWS_PER_QUERY), + '#description' => t('Controls how many records are loaded from the database at once. Defaults to "!default", i.e. !default rows. Note that setting this to a high number can cause problems when exporting large data sets, e.g. cache tables can have huge volumes of data per record.', array('!default' => '' . check_plain(BACKUP_MIGRATE_DATA_ROWS_PER_QUERY) . '')), + '#required' => FALSE, + '#element_validate' => array('element_validate_integer_positive'), + ); + $form['backup_migrate_data_rows_per_line'] = array( + '#type' => 'textfield', + '#title' => t('Rows Per Line'), + '#default_value' => variable_get('backup_migrate_data_rows_per_line', BACKUP_MIGRATE_DATA_ROWS_PER_LINE), + '#description' => t('Controls how many records are included in a single INSERT statement. Defaults to "!default", i.e. !default records.', array('!default' => '' . check_plain(BACKUP_MIGRATE_DATA_ROWS_PER_LINE) . '')), + '#required' => FALSE, + '#element_validate' => array('element_validate_integer_positive'), + ); + $form['backup_migrate_data_bytes_per_line'] = array( + '#type' => 'textfield', + '#title' => t('Data Bytes Per Line'), + '#default_value' => variable_get('backup_migrate_data_bytes_per_line', BACKUP_MIGRATE_DATA_BYTES_PER_LINE), + '#description' => t('Controls how much data will be inserted at once using a single INSERT statement. This works with the "backup_migrate_data_rows_per_line" variable to ensure that each INSERT statement doesn\'t end up being too large. Defaults to "!default", i.e. !default bytes.', array('!default' => '' . check_plain(BACKUP_MIGRATE_DATA_BYTES_PER_LINE) . '')), + '#required' => FALSE, + '#element_validate' => array('element_validate_integer_positive'), + ); + $form['backup_migrate_max_email_size'] = array( + '#type' => 'textfield', + '#title' => t('Maximum E-mail Size'), + '#default_value' => variable_get('backup_migrate_max_email_size', BACKUP_MIGRATE_MAX_EMAIL_SIZE), + '#description' => t('In bytes. Limits the size of e-mails with attached back-ups that can be sent. Default: !default.', array('!default' => '' . check_plain(BACKUP_MIGRATE_MAX_EMAIL_SIZE) . '')), + '#required' => FALSE, + '#element_validate' => array('backup_migrate_unsigned_integer_validate'), + ); + $form['backup_migrate_cleanup_temp_files'] = array( + '#type' => 'checkbox', + '#title' => t('Clean up Temporary Files'), + '#default_value' => variable_get('backup_migrate_cleanup_temp_files', BACKUP_MIGRATE_CLEANUP_TEMP_FILES), + '#description' => t('Should temporary files be deleted during the current back-up run? Default: !default.', array('!default' => (BACKUP_MIGRATE_CLEANUP_TEMP_FILES ? 'on' : 'off'))), + '#required' => FALSE, + ); + $form['backup_migrate_cleanup_time'] = array( + '#type' => 'textfield', + '#title' => t('Clean-up Time'), + '#default_value' => variable_get('backup_migrate_cleanup_time', BACKUP_MIGRATE_CLEANUP_TIME), + '#description' => t('Delete temporary files belonging to this modules that are older than the expire time. In seconds. 21600 seconds is 6 hours. Default: !default.', array('!default' => '' . check_plain(BACKUP_MIGRATE_CLEANUP_TIME) . '')), + '#required' => FALSE, + '#element_validate' => array('element_validate_integer'), + ); + $form['backup_migrate_schedule_buffer'] = array( + '#type' => 'textfield', + '#title' => t('Schedule buffer'), + '#default_value' => variable_get('backup_migrate_schedule_buffer', BACKUP_MIGRATE_SCHEDULE_BUFFER), + '#description' => t('Add a small negative buffer (for example 1% of the entire period) to the time to account for slight difference in cron run length. Format: percentage of the period as a decimal fraction between 0 and 1. Default: !default.', array('!default' => '' . check_plain(BACKUP_MIGRATE_SCHEDULE_BUFFER) . '')), + '#required' => FALSE, + '#element_validate' => array('backup_migrate_fraction_validate'), + ); + + // Check for overrides and report them to the user. + $keys = array_keys($form); + $query = db_query('SELECT name, value FROM {variable} WHERE name IN (:keys)', array(':keys' => $keys)); + $result = $query->fetchAllKeyed(); + foreach ($form as $key => $field) { + if (isset($result[$key])) { + // Compare the value with what we have in the database. + $db_value = unserialize($result[$key]); + if ($field['#default_value'] != $db_value) { + $form[$key]['#description'] .= ' Overridden in settings.php: ' . check_plain($field['#default_value']) . '.'; + } + } + } + + $form['actions'] = array('#type' => 'actions'); + $form['actions']['submit'] = array( + '#type' => 'submit', + '#value' => t('Save configuration'), + ); + + return $form; +} + +/** + * The advanced settings form. + */ +function backup_migrate_ui_advanced_settings_form_submit($form, &$form_state) { + $variables = array( + 'backup_migrate_backup_max_time' => BACKUP_MIGRATE_BACKUP_MAX_TIME, + 'backup_migrate_cleanup_temp_files' => BACKUP_MIGRATE_CLEANUP_TEMP_FILES, + 'backup_migrate_cleanup_time' => BACKUP_MIGRATE_CLEANUP_TIME, + 'backup_migrate_data_bytes_per_line' => BACKUP_MIGRATE_DATA_BYTES_PER_LINE, + 'backup_migrate_data_rows_per_line' => BACKUP_MIGRATE_DATA_ROWS_PER_LINE, + 'backup_migrate_data_rows_per_query' => BACKUP_MIGRATE_DATA_ROWS_PER_QUERY, + 'backup_migrate_disable_cron' => BACKUP_MIGRATE_DISABLE_CRON, + 'backup_migrate_max_email_size' => BACKUP_MIGRATE_MAX_EMAIL_SIZE, + 'backup_migrate_memory_limit' => BACKUP_MIGRATE_MEMORY_LIMIT, + 'backup_migrate_schedule_buffer' => BACKUP_MIGRATE_SCHEDULE_BUFFER, + 'backup_migrate_verbose' => BACKUP_MIGRATE_VERBOSE, + ); + foreach ($variables as $var_name => $var_default) { + if (isset($form_state['values'][$var_name])) { + // There's no need to save the variable if it's just the default. + if ($form_state['values'][$var_name] == $var_default) { + variable_del($var_name); + } + // The variable is not the default, so save it. + else { + variable_set($var_name, $form_state['values'][$var_name]); + } + } + } +} + +/** + * Validate fields that require a PHP.ini-like memory_limit value. + */ +function backup_migrate_memory_limit_validate($element, &$form_state, $form) { + if (!_backup_migrate_is_valid_memory_limit($element['#value'])) { + form_error($element, t('%name must be a valid memory limit setting.', array('%name' => $element['#title']))); + } +} + +/** + * Validate fields that require zero or a positive integer. + */ +function backup_migrate_unsigned_integer_validate($element, &$form_state, $form) { + $value = $element['#value']; + $test = preg_match('/^[0-9]+$/', $value); + if ($test !== 1) { + form_error($element, t('%name must be zero or a positive integer.', array('%name' => $element['#title']))); + } +} + +/** + * Validate fields that require a decimal between 0 and 1. + * + * @todo Better name. + */ +function backup_migrate_fraction_validate($element, &$form_state, $form) { + $value = $element['#value']; + $test = preg_match('/^(0|1|0?\.[0-9]+)$/', $value); + if ($test !== 1) { + form_error($element, t('%name must be a decimal between 0 and 1.', array('%name' => $element['#title']))); + } +} diff --git a/sites/all/modules/contrib/admin/backup_migrate/backup_migrate.css b/sites/all/modules/contrib/admin/backup_migrate/backup_migrate.css index 27250bc..46c5795 100644 --- a/sites/all/modules/contrib/admin/backup_migrate/backup_migrate.css +++ b/sites/all/modules/contrib/admin/backup_migrate/backup_migrate.css @@ -1,5 +1,10 @@ +/** + * @file + * Custom CSS for the Backup Migrate module. + */ + .schedule-list-disabled { - filter:alpha(opacity=50); + filter: alpha(opacity=50); -moz-opacity: .50; opacity: .50; } @@ -8,7 +13,7 @@ font-size: 0.85em; } -.backup-migrate-tables-checkboxes .form-item label { +.backup-migrate-tables-checkboxes .form-item { width: 15em; float: left; overflow: hidden; @@ -16,24 +21,50 @@ height: 1.75em; margin: .25em .25em 0 0; } -.backup-migrate-tables-checkboxes .form-item label.checked { - background-color: #eee; -} .backup-migrate-tables-checkboxes .form-item label input { - margin-right: .25em; + margin-right: .5em; } div.backup-migrate-tables-checkboxes { - max-height: 40em; + height: 20em; overflow: auto; } -div.backup-migrate-description { +div.backup-migrate-tags, +div.backup-migrate-description, +div.backup-migrate-date { font-size: smaller; } - +span.backup-migrate-label { + font-weight: bold; +} +table.backup-migrate-listing td { + vertical-align: top; +} div.row-error { - color: #FF0000; + color: #ff0000; font-weight: bold; } +.backup-migrate-listing { + margin-bottom: 2em; +} +.backup-migrate-actions { + text-align: right; +} + +html.js #backup-migrate-ui-manual-quick-backup-form fieldset.collapsed, +.backup-migrate-inline { + margin-bottom: 1em; +} + +.backup-migrate-form-dependent { + padding-left: 2em; + padding-bottom: 1em; +} +.backup-migrate-form-dependent .form-item { + margin-top: 0; +} +.backup-migrate-footer-message { + margin-bottom: 1em; +} diff --git a/sites/all/modules/contrib/admin/backup_migrate/backup_migrate.info b/sites/all/modules/contrib/admin/backup_migrate/backup_migrate.info index 02c716f..a7d0183 100644 --- a/sites/all/modules/contrib/admin/backup_migrate/backup_migrate.info +++ b/sites/all/modules/contrib/admin/backup_migrate/backup_migrate.info @@ -1,17 +1,52 @@ name = Backup and Migrate -description = "Backup or migrate the Drupal Database quickly and without unnecessary data." +description = "Backup the Drupal database and files or migrate them to another environment." core = 7.x +configure = admin/config/system/backup_migrate -files[] = backup_migrate.module -files[] = backup_migrate.install +; Require PHP 5.4 so that some slightly more advanced PHP logic can be used. +; This is for future usage; removing this will allow the module to continue +; working fine for now. +php = 5.4 + +; All of the class files used within the module. +files[] = includes/crud.inc files[] = includes/destinations.inc +files[] = includes/destinations.browser.inc +files[] = includes/destinations.db.inc +files[] = includes/destinations.db.mysql.inc +files[] = includes/destinations.email.inc +files[] = includes/destinations.file.inc +files[] = includes/destinations.ftp.inc +files[] = includes/destinations.s3.inc +files[] = includes/files.inc +files[] = includes/filters.inc +files[] = includes/filters.backup_restore.inc +files[] = includes/filters.compression.inc +files[] = includes/filters.encryption.inc +files[] = includes/filters.statusnotify.inc +files[] = includes/filters.utils.inc +files[] = includes/locations.inc files[] = includes/profiles.inc files[] = includes/schedules.inc +files[] = includes/sources.inc +files[] = includes/sources.archivesource.inc +files[] = includes/sources.db.inc +files[] = includes/sources.db.mysql.inc +files[] = includes/sources.filesource.inc -configure = admin/config/system/backup_migrate -; Information added by drupal.org packaging script on 2013-04-10 -version = "7.x-2.5" +; Test dependencies. +test_dependencies[] = ctools + +; Test suite. +files[] = tests/BmTestBase.test +files[] = tests/BmTestBasics.test +files[] = tests/BmTestProfiles.test +files[] = tests/BmTestEmail.test +files[] = tests/BmTestCtools.test +files[] = tests/BmTestUpdate7310.test + +; Information added by Drupal.org packaging script on 2020-07-15 +version = "7.x-3.9" core = "7.x" project = "backup_migrate" -datestamp = "1365564017" - +datestamp = "1594807678" diff --git a/sites/all/modules/contrib/admin/backup_migrate/backup_migrate.install b/sites/all/modules/contrib/admin/backup_migrate/backup_migrate.install index 0e5647b..0d550fc 100644 --- a/sites/all/modules/contrib/admin/backup_migrate/backup_migrate.install +++ b/sites/all/modules/contrib/admin/backup_migrate/backup_migrate.install @@ -1,29 +1,171 @@ $destination) { + if (method_exists($destination, 'get_display_location')) { + $dest_path = $destination->get_display_location(); + if (!empty($dest_path) && file_valid_uri($dest_path)) { + $scheme = file_uri_scheme($dest_path); + // Support public and private storage and raw server paths. + if ($scheme === 'private' || $scheme === 'public' || substr($dest_path, 0, 1) == '/') { + // Check if the path exists. + $path_exists = file_prepare_directory($dest_path, FILE_CREATE_DIRECTORY); + if ($path_exists) { + $real_path = drupal_realpath($dest_path); + // See if the private path is somewhere inside the main Drupal + // directory structure. + if (strpos($real_path, DRUPAL_ROOT) === 0) { + // Extract the relative path from the Drupal root path, and + // then add the base URL, theoretically creating a fully + // qualified URL to the storage directory. + $url = substr($real_path, strlen(DRUPAL_ROOT) + 1); + $url = url($url, array('absolute' => TRUE)); + $result = drupal_http_request($url); + + // If the HTTP request comes back as a status 200 that means + // there is a directory listing of some sort; directory paths + // should return a 503 error. + if (!empty($result->code) && $result->code == 200) { + // Get the human readable information for this destination. + $dest_spec = $destination->get_list_row(); + + // Display a warning message. + $requirements['bmdest_' . $dest_name] = array( + 'severity' => REQUIREMENT_ERROR, + 'title' => 'Backup Migrate', + 'value' => $t('Backup destination "%dest" is publicly accessible!', array('%dest' => $dest_spec['name'])), + 'description' => $t('The backup destination, "%dest", stores its files in the "%path" directory. This directory is publicly available from the web server and urgently needs to be secured! Please see the Drupal manual on configuring the private directory path on how to fix this problem.', + array( + '%dest' => $dest_spec['name'], + '%path' => $real_path, + '@manual' => 'https://www.drupal.org/docs/7/core/modules/file/overview', + )), + ); + } + + // Check an individual file. + else { + $files = scandir($real_path); + if (!empty($files)) { + foreach ($files as $file) { + // Skip the base field pointers. + if ($file == '.' || $file == '..') { + continue; + } + + $result = drupal_http_request($url . '/' . $file); + + // If the HTTP request comes back as a status 200 that + // means the file is accessible. + if (!empty($result->code) && $result->code == 200) { + // Get the human readable information for this + // destination. + $dest_spec = $destination->get_list_row(); + + // Display a warning message. + $requirements['bmdest_' . $dest_name] = array( + 'severity' => REQUIREMENT_ERROR, + 'title' => 'Backup Migrate', + 'value' => $t('Files in "%dest" are publicly accessible!', array('%dest' => $dest_spec['name'])), + 'description' => $t('The backup destination, "%dest", stores its files in the "%path" directory. These file(s) are publicly available from the web server and urgently need to be secured! Please see the Drupal manual on configuring the private directory path on how to fix this problem.', + array( + '%dest' => $dest_spec['name'], + '%path' => $real_path, + '@manual' => 'https://www.drupal.org/docs/7/core/modules/file/overview', + )), + ); + } + + // Only need to check one file. + break; + } + } + } + } + } + } + } + } + } + // Leave a note if there were no problems. + // @todo No point in displaying this until the API has been expanded. + // @code + // if (empty($requirements)) { + // $requirements['bmdest_' . $dest_name] = array( + // 'severity' => REQUIREMENT_INFO, + // 'title' => 'Backup Migrate', + // 'value' => $t('Backup destinations are safe'), + // 'description' => $t('The backup destinations were all checked and none of them were exposing files to the public. This is a good thing.'), + // ); + // } + // @endcode + + if (variable_get('backup_migrate_disable_cron', FALSE)) { + $requirements['bm_disable_cron'] = array( + 'severity' => REQUIREMENT_INFO, + 'title' => 'Backup Migrate', + 'value' => $t('Cron tasks are disabled'), + 'description' => $t('The cron tasks have been disabled, so scheduled backups will not run. See the Backup & Migrate module\'s README.txt file for further details.'), + ); + } + } + return $requirements; } /** - * Implementation of hook_schema(). + * Implements hook_schema(). */ function backup_migrate_schema() { $schema['backup_migrate_profiles'] = array( + 'export' => array( + 'key' => 'machine_name', + 'key name' => 'Profile ID', + 'admin_title' => 'name', + 'primary key' => 'profile_id', + // Exports will be defined as $preset. + 'identifier' => 'item', + // Function hook name. + 'default hook' => 'exportables_backup_migrate_profiles', + 'api' => array( + 'owner' => 'backup_migrate', + // Base name for api include files. + 'api' => 'backup_migrate_exportables', + 'minimum_version' => 1, + 'current_version' => 1, + ), + ), 'fields' => array( 'profile_id' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => 'Primary ID field for the table. Not used for anything except internal lookups.', + // Do not export database-only keys. + 'no export' => TRUE, + ), + 'machine_name' => array( 'type' => 'varchar', - 'length' => 32, + 'length' => 255, 'not null' => TRUE, 'default' => '0', 'description' => 'The primary identifier for a profile.', @@ -32,13 +174,13 @@ function backup_migrate_schema() { 'description' => 'The name of the profile.', 'type' => 'varchar', 'length' => 255, - 'not null' => TRUE + 'not null' => TRUE, ), 'filename' => array( 'description' => 'The name of the profile.', 'type' => 'varchar', 'length' => 255, - 'not null' => TRUE + 'not null' => TRUE, ), 'append_timestamp' => array( 'description' => 'Append a timestamp to the filename.', @@ -46,17 +188,18 @@ function backup_migrate_schema() { 'size' => 'tiny', 'unsigned' => TRUE, 'not null' => TRUE, - 'default' => 0 + 'default' => 0, ), 'timestamp_format' => array( 'description' => 'The format of the timestamp.', 'type' => 'varchar', 'length' => 14, - 'not null' => TRUE + 'not null' => TRUE, ), 'filters' => array( 'description' => 'The filter settings for the profile.', 'type' => 'text', + 'size' => 'medium', 'not null' => TRUE, 'serialize' => TRUE, 'serialized default' => 'a:0:{}', @@ -65,11 +208,159 @@ function backup_migrate_schema() { 'primary key' => array('profile_id'), ); $schema['backup_migrate_destinations'] = array( + 'export' => array( + 'key' => 'machine_name', + 'key name' => 'Destination ID', + 'admin_title' => 'name', + 'primary key' => 'destination_id', + // Exports will be defined as $preset. + 'identifier' => 'item', + // Function hook name. + 'default hook' => 'exportables_backup_migrate_destinations', + 'api' => array( + 'owner' => 'backup_migrate', + // Base name for api include files. + 'api' => 'backup_migrate_exportables', + 'minimum_version' => 1, + 'current_version' => 1, + ), + ), 'fields' => array( 'destination_id' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => 'Primary ID field for the table. Not used for anything except internal lookups.', + // Do not export database-only keys. + 'no export' => TRUE, + ), + 'machine_name' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '0', + 'description' => 'The primary identifier for a destination.', + ), + 'name' => array( + 'description' => 'The name of the destination.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + ), + 'subtype' => array( + 'description' => 'The type of the destination.', 'type' => 'varchar', 'length' => 32, 'not null' => TRUE, + ), + 'location' => array( + 'description' => 'The the location string of the destination.', + 'type' => 'text', + 'not null' => TRUE, + ), + 'settings' => array( + 'description' => 'Other settings for the destination.', + 'type' => 'text', + 'not null' => TRUE, + 'serialize' => TRUE, + 'serialized default' => 'a:0:{}', + ), + ), + 'primary key' => array('destination_id'), + ); + $schema['backup_migrate_sources'] = array( + 'export' => array( + 'key' => 'machine_name', + 'key name' => 'Source ID', + 'admin_title' => 'name', + 'primary key' => 'source_id', + // Exports will be defined as $preset. + 'identifier' => 'item', + // Function hook name. + 'default hook' => 'exportables_backup_migrate_sources', + 'api' => array( + 'owner' => 'backup_migrate', + // Base name for api include files. + 'api' => 'backup_migrate_exportables', + 'minimum_version' => 1, + 'current_version' => 1, + ), + ), + 'fields' => array( + 'source_id' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => 'Primary ID field for the table. Not used for anything except internal lookups.', + // Do not export database-only keys. + 'no export' => TRUE, + ), + 'machine_name' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '0', + 'description' => 'The primary identifier for a source.', + ), + 'name' => array( + 'description' => 'The name of the source.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + ), + 'subtype' => array( + 'description' => 'The type of the source.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + ), + 'location' => array( + 'description' => 'The the location string of the source.', + 'type' => 'text', + 'not null' => TRUE, + ), + 'settings' => array( + 'description' => 'Other settings for the source.', + 'type' => 'text', + 'not null' => TRUE, + 'serialize' => TRUE, + 'serialized default' => 'a:0:{}', + ), + ), + 'primary key' => array('source_id'), + ); + + $schema['backup_migrate_schedules'] = array( + 'export' => array( + 'key' => 'machine_name', + 'key name' => 'Source ID', + 'admin_title' => 'name', + 'primary key' => 'schedule_id', + // Exports will be defined as $preset. + 'identifier' => 'item', + // Function hook name. + 'default hook' => 'exportables_backup_migrate_schedules', + 'api' => array( + 'owner' => 'backup_migrate', + // Base name for api include files. + 'api' => 'backup_migrate_exportables', + 'minimum_version' => 1, + 'current_version' => 1, + ), + ), + 'fields' => array( + 'schedule_id' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => 'Primary ID field for the table. Not used for anything except internal lookups.', + // Do not export database-only keys. + 'no export' => TRUE, + ), + 'machine_name' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, 'default' => '0', 'description' => 'The primary identifier for a profile.', ), @@ -77,63 +368,34 @@ function backup_migrate_schema() { 'description' => 'The name of the profile.', 'type' => 'varchar', 'length' => 255, - 'not null' => TRUE - ), - 'type' => array( - 'description' => 'The type of the destination.', - 'type' => 'varchar', - 'length' => 32, - 'not null' => TRUE - ), - 'location' => array( - 'description' => 'The the location string of the destination.', - 'type' => 'text', - 'not null' => TRUE - ), - 'settings' => array( - 'description' => 'Other settings for the destination.', - 'type' => 'text', 'not null' => TRUE, - 'serialize' => TRUE, - 'serialized default' => 'a:0:{}', - ), - ), - 'primary key' => array('destination_id'), - ); - $schema['backup_migrate_schedules'] = array( - 'fields' => array( - 'schedule_id' => array( - 'type' => 'varchar', - 'length' => 32, - 'not null' => TRUE, - 'default' => '0', - 'description' => 'The primary identifier for a profile.', - ), - 'name' => array( - 'description' => 'The name of the profile.', - 'type' => 'varchar', - 'length' => 255, - 'not null' => TRUE ), 'source_id' => array( 'description' => 'The {backup_migrate_destination}.destination_id of the source to backup from.', 'type' => 'varchar', - 'length' => 32, + 'length' => 255, 'default' => 'db', - 'not null' => TRUE + 'not null' => TRUE, ), 'destination_id' => array( 'type' => 'varchar', - 'length' => 32, + 'length' => 255, 'not null' => TRUE, 'default' => '0', 'description' => 'The {backup_migrate_destination}.destination_id of the destination to back up to.', ), - 'profile_id' => array( + 'copy_destination_id' => array( 'type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => '0', + 'description' => 'A second {backup_migrate_destination}.destination_id of the destination to copy the backup to.', + ), + 'profile_id' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '0', 'description' => 'The primary identifier for a profile.', ), 'keep' => array( @@ -154,15 +416,21 @@ function backup_migrate_schema() { 'size' => 'tiny', 'unsigned' => TRUE, 'not null' => TRUE, - 'default' => 0 + 'default' => 0, ), 'cron' => array( 'description' => 'Whether the schedule should be run during cron.', - 'type' => 'int', - 'size' => 'tiny', - 'unsigned' => TRUE, + 'type' => 'varchar', + 'length' => 32, 'not null' => TRUE, - 'default' => 0 + 'default' => 'builtin', + ), + 'cron_schedule' => array( + 'description' => 'The cron schedule to run on.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '0 4 * * *', ), ), 'primary key' => array('schedule_id'), @@ -172,58 +440,41 @@ function backup_migrate_schema() { } /** - * Implementation of hook_install(). - */ -function backup_migrate_install() { - _backup_migrate_setup_database_defaults(); -} - -function _backup_migrate_setup_database_defaults() { - if (variable_get("backup_migrate_file_name", NULL)) { - require_once DRUPAL_ROOT . '/'. drupal_get_path('module', 'backup_migrate') .'/includes/crud.inc'; - require_once DRUPAL_ROOT . '/'. drupal_get_path('module', 'backup_migrate') .'/backup_migrate.module'; - require_once DRUPAL_ROOT . '/'. drupal_get_path('module', 'backup_migrate') .'/includes/profiles.inc'; - require_once DRUPAL_ROOT . '/'. drupal_get_path('module', 'backup_migrate') .'/includes/files.inc'; - - $settings = array( - 'profile_id' => 'default', - 'filename' => variable_get("backup_migrate_file_name", _backup_migrate_default_filename()), - 'append_timestamp' => variable_get("backup_migrate_append_timestamp", FALSE) ? 1 : 0, - 'timestamp_format' => variable_get("backup_migrate_timestamp_format", 'Y-m-d\TH-i-s'), - 'filters' => array( - 'compression' => variable_get("backup_migrate_compression", "none"), - 'exclude_tables' => variable_get("backup_migrate_exclude_tables", array()), - 'nodata_tables' => variable_get("backup_migrate_nodata_tables", array()), - ), - 'name' => t('Default Settings'), - ); - $profile = backup_migrate_crud_create_item('profile', $settings); - $profile->save(); - variable_set("backup_migrate_profile_id", 'default'); - - // Set up the default schedules. - if (variable_get("backup_migrate_schedule_backup_period", 0)) { - require_once DRUPAL_ROOT . '/'. drupal_get_path('module', 'backup_migrate') .'/includes/schedules.inc'; - $schedule = array( - 'name' => t('Default Schedule'), - 'profile_id' => $profile->get_id(), - 'enabled' => 1, - 'destination_id' => 'scheduled', - 'period' => array('number' => variable_get("backup_migrate_schedule_backup_period", 0), 'type' => 'hours'), - 'keep' => variable_get("backup_migrate_schedule_backup_keep", 0), - ); - $schedule = backup_migrate_crud_create_item('schedule', $schedule); - $schedule->save(); - } - } -} - -/** - * Remove variables on uninstall. + * Implements hook_uninstall(). */ function backup_migrate_uninstall() { - db_query("DELETE FROM {variable} WHERE name LIKE 'backup_migrate_%'"); - cache_clear_all('variables', 'cache'); + variable_del('backup_migrate_backup_max_time'); + variable_del('backup_migrate_cleanup_temp_files'); + variable_del('backup_migrate_cleanup_time'); + variable_del('backup_migrate_copy_destination_id'); + variable_del('backup_migrate_data_bytes_per_line'); + variable_del('backup_migrate_data_rows_per_line'); + variable_del('backup_migrate_data_rows_per_query'); + variable_del('backup_migrate_destination_id'); + variable_del('backup_migrate_disable_cron'); + variable_del('backup_migrate_max_email_size'); + variable_del('backup_migrate_memory_limit'); + variable_del('backup_migrate_profile_id'); + variable_del('backup_migrate_schedule_buffer'); + variable_del('backup_migrate_schedule_last_run_'); + variable_del('backup_migrate_source_id'); + variable_del('backup_migrate_timeout_buffer'); + variable_del('backup_migrate_verbose'); + variable_del('nodesquirrel_endpoint_urls'); + variable_del('nodesquirrel_schedule'); + variable_del('nodesquirrel_schedule_enabled'); + variable_del('nodesquirrel_schedule_source_id'); + variable_del('nodesquirrel_secret_key'); + + // Remove the dynamic generated 'last run' variables. + $last_ran_schedules = db_select('variable', 'var') + ->fields('var', array('name')) + ->condition('name', 'backup\_migrate\_schedule\_last\_run\_%', 'LIKE') + ->execute() + ->fetchAllAssoc('name', PDO::FETCH_ASSOC); + foreach ($last_ran_schedules as $key => $schedule) { + variable_del($key); + } } /** @@ -235,7 +486,7 @@ function backup_migrate_update_2000() { } /** - * Adding filter field for dev release of 2009-01-28 + * Adding filter field for dev release of 2009-01-28. */ function backup_migrate_update_2001() { $ret = array(); @@ -243,11 +494,21 @@ function backup_migrate_update_2001() { // Add the filters field to the db. if (!db_field_exists('backup_migrate_profiles', 'filters')) { - db_add_field('backup_migrate_profiles', 'filters', array('description' => t('The filter settings for the profile.'),'type' => 'text', 'not null' => TRUE)); + db_add_field('backup_migrate_profiles', 'filters', array( + 'description' => t('The filter settings for the profile.'), + 'type' => 'text', + 'not null' => TRUE, + )); } - // Add the source field + // Add the source field. if (!db_field_exists('backup_migrate_profiles', 'source_id')) { - db_add_field('backup_migrate_profiles', 'source_id', array('description' => t('The {backup_migrate_destination}.destination_id of the source to backup from.'), 'type' => 'varchar', 'length' => 32, 'default' => 'db', 'not null' => TRUE)); + db_add_field('backup_migrate_profiles', 'source_id', array( + 'description' => t('The {backup_migrate_destination}.destination_id of the source to backup from.'), + 'type' => 'varchar', + 'length' => 255, + 'default' => 'db', + 'not null' => TRUE, + )); } // Remove the compression field. if (db_field_exists('backup_migrate_profiles', 'compression')) { @@ -257,7 +518,7 @@ function backup_migrate_update_2001() { } /** - * Clearing the cache because there was a menu structure change in the dev of 2009-05-31 + * Clear the cache because there was a menu structure change. */ function backup_migrate_update_2002() { // Cache should clear automatically. Nothing to do here. @@ -265,13 +526,13 @@ function backup_migrate_update_2002() { } /** - * Allowing non-int profile ids in schedules 2009-05-31 + * Allowing non-int profile ids in schedules 2009-05-31. */ function backup_migrate_update_2003() { $ret = array(); $spec = array( 'type' => 'varchar', - 'length' => 32, + 'length' => 255, 'not null' => TRUE, 'default' => '0', 'description' => 'The primary identifier for a profile.', @@ -282,13 +543,13 @@ function backup_migrate_update_2003() { } /** - * Allowing non-int profile ids 2009-07-01 + * Allowing non-int profile ids 2009-07-01. */ function backup_migrate_update_2004() { $ret = array(); $spec = array( 'type' => 'varchar', - 'length' => 32, + 'length' => 255, 'not null' => TRUE, 'default' => '0', ); @@ -314,16 +575,16 @@ function backup_migrate_update_2004() { } /** - * Change the default database id to something friendlier 2009-08-08 + * Change the default database id to something friendlier 2009-08-08. */ function backup_migrate_update_2005() { - require_once './'. drupal_get_path('module', 'backup_migrate') .'/includes/crud.inc'; - require_once './'. drupal_get_path('module', 'backup_migrate') .'/includes/profiles.inc'; + require_once './' . drupal_get_path('module', 'backup_migrate') . '/includes/crud.inc'; + require_once './' . drupal_get_path('module', 'backup_migrate') . '/includes/profiles.inc'; $ret = array(); - // Change the destination ids of the defined database sources mostly to make using them with drush friendlier. - - // Change the db_url:default id to simply 'db' + // Change the destination ids of the defined database sources mostly to make + // using them with drush friendlier. + // Change the db_url:default id to simply 'db'. $ret[] = db_query("UPDATE {backup_migrate_profiles} SET source_id = 'db' WHERE source_id = 'db_url:default'"); $ret[] = db_query("UPDATE {backup_migrate_schedules} SET destination_id = 'db' WHERE destination_id = 'db_url:default'"); @@ -331,9 +592,15 @@ function backup_migrate_update_2005() { $ret[] = db_query("UPDATE {backup_migrate_profiles} SET source_id = REPLACE(source_id, 'db_url:', 'db:')"); $ret[] = db_query("UPDATE {backup_migrate_schedules} SET destination_id = REPLACE(destination_id, 'db_url:', 'db:')"); - // Add the source field to the schedule + // Add the source field to the schedule. if (!db_field_exists('backup_migrate_schedules', 'source_id')) { - db_add_field('backup_migrate_schedules', 'source_id', array('description' => t('The db source to backup from.'), 'type' => 'varchar', 'length' => 32, 'default' => 'db', 'not null' => TRUE)); + db_add_field('backup_migrate_schedules', 'source_id', array( + 'description' => t('The db source to backup from.'), + 'type' => 'varchar', + 'length' => 255, + 'default' => 'db', + 'not null' => TRUE, + )); } // Copy source data from profiles to schedules. @@ -342,7 +609,7 @@ function backup_migrate_update_2005() { if (!$schedule['source_id']) { $schedule['source_id'] = 'db'; } - $ret[] = db_query("UPDATE {backup_migrate_schedules} SET source_id = '". $schedule['source_id'] ."' WHERE schedule_id = '". $schedule['profile_id'] ."'"); + $ret[] = db_query("UPDATE {backup_migrate_schedules} SET source_id = '" . $schedule['source_id'] . "' WHERE schedule_id = '" . $schedule['profile_id'] . "'"); } if (db_field_exists('backup_migrate_profiles', 'source_id')) { @@ -382,19 +649,21 @@ function backup_migrate_update_7200() { } } - /** * Change the filename field to support 255 characters. */ function backup_migrate_update_7202() { $ret = array(); - db_change_field('backup_migrate_profiles', 'filename', 'filename', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE)); + db_change_field('backup_migrate_profiles', 'filename', 'filename', array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + )); return $ret; } - /** - * Update the schedule last run times to use variables instead of saving with the schedule. + * Schedule last run times to use variables instead of saving with the schedule. */ function backup_migrate_update_7203() { $result = db_query('SELECT * FROM {backup_migrate_schedules}', array(), array('fetch' => PDO::FETCH_ASSOC)); @@ -407,3 +676,393 @@ function backup_migrate_update_7203() { db_drop_field('backup_migrate_schedules', 'last_run'); } } + +/** + * Upgrade from Backup & Migrate 7.x-2.x. + * + * - Uninstall the Backup Migrate Files module if it's installed. + * - Uninstall the NodeSquirrel module if it's installed. + * - Upgrade the configurations. + */ +function backup_migrate_update_7300() { + if (module_exists('backup_migrate_files')) { + module_disable(array('backup_migrate_files')); + $ret[] = array( + 'success' => TRUE, + 'query' => 'Disabled the Backup and Migrate Files module', + ); + } + if (module_exists('nodesquirrel')) { + module_disable(array('nodesquirrel')); + $ret[] = array( + 'success' => TRUE, + 'query' => 'Disabled the NodeSquirrel module', + ); + } + + // Add sources to the schema. + $schema['backup_migrate_sources'] = array( + 'fields' => array( + 'source_id' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '0', + 'description' => t('The primary identifier for a source.'), + ), + 'name' => array( + 'description' => t('The name of the source.'), + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + ), + 'type' => array( + 'description' => t('The type of the source.'), + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + ), + 'location' => array( + 'description' => t('The the location string of the source.'), + 'type' => 'text', + 'not null' => TRUE, + ), + 'settings' => array( + 'description' => t('Other settings for the source.'), + 'type' => 'text', + 'not null' => TRUE, + 'serialize' => TRUE, + 'serialized default' => 'a:0:{}', + ), + ), + 'primary key' => array('source_id'), + ); + + if (!db_table_exists('backup_migrate_sources')) { + db_create_table('backup_migrate_sources', $schema['backup_migrate_sources']); + } + + // Move custom destinations to sources. + $result = db_query("SELECT * FROM {backup_migrate_destinations} WHERE type in ('filesource', 'db')", array(), array('fetch' => PDO::FETCH_ASSOC)); + foreach ($result as $item) { + $item['source_id'] = $item['destination_id']; + drupal_write_record('backup_migrate_source', $item); + } + + // Change 'destination' settings to 'source' settings. + $result = db_query('SELECT * FROM {backup_migrate_profiles}', array(), array('fetch' => PDO::FETCH_ASSOC)); + foreach ($result as $item) { + $item['filters'] = unserialize($item['filters']); + $item['filters']['sources'] = $item['filters']['destinations']; + unset($item['filters']['destinations']); + drupal_write_record('backup_migrate_profiles', $item, array('profile_id')); + } + + // Clear the plugins caches. + cache_clear_all('*', 'cache', TRUE); + + // Rebuild the menus. + variable_set('menu_rebuild_needed', TRUE); +} + +/** + * Switch the cron switch to text. + */ +function backup_migrate_update_7301() { + db_change_field('backup_migrate_schedules', 'cron', 'cron', array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => 'builtin', + )); + db_add_field('backup_migrate_schedules', 'cron_schedule', array( + 'description' => 'The cron schedule to run on.', + 'type' => 'varchar', + 'length' => 255, + 'default' => '0 4 * * *', + 'not null' => TRUE, + )); +} + +/** + * Add a second destination to schedules. + */ +function backup_migrate_update_7302() { + db_add_field('backup_migrate_schedules', 'copy_destination_id', + array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '0', + 'description' => 'A second {backup_migrate_destination}.destination_id of the destination to copy the backup to.', + ) + ); +} + +/** + * Add a serial id field to all tables to allow them to be ctools exportable. + */ +function backup_migrate_update_7303() { + foreach (array( + 'backup_migrate_sources' => 'source_id', + 'backup_migrate_destinations' => 'destination_id', + 'backup_migrate_schedules' => 'schedule_id', + 'backup_migrate_profiles' => 'profile_id', + ) as $table => $id) { + // Take the primary key status from the machine name so it can be renamed + // A serial field must be defined as a key, so make a temporary index. + // See: https://www.drupal.org/node/190027 + db_add_index($table, 'temp', array($id)); + db_drop_primary_key($table); + // Drop our temporary index. + db_drop_index($table, 'temp'); + + // Switch the name of the id to 'machine_name' to be more ctools standard. + db_change_field($table, $id, 'machine_name', array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + )); + + // Add a serial ID. + db_add_field($table, $id, + array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => 'Primary ID field for the table. Not used for anything except internal lookups.', + // Do not export database-only keys. + 'no export' => TRUE, + ), + array('primary key' => array($id)) + ); + } + foreach (array('backup_migrate_sources', 'backup_migrate_destinations') as $table) { + db_change_field($table, 'type', 'subtype', array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + )); + } +} + +/** + * Update all schedules to use the built in cron if none is specified. + */ +function backup_migrate_update_7304() { + db_query("UPDATE {backup_migrate_schedules} SET cron = 'builtin' WHERE cron = '0'"); +} + +/** + * Fix schema mismatch after upgrade. + */ +function backup_migrate_update_7305() { + // Fix the 'machine_name' table fields. + $field_spec = array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '0', + ); + foreach (array( + 'backup_migrate_profiles', + 'backup_migrate_destinations', + 'backup_migrate_sources', + 'backup_migrate_schedules', + ) as $table) { + if (!db_field_exists($table, 'machine_name')) { + try { + db_add_field($table, 'machine_name', $field_spec); + } + catch (\Exception $e) { + db_change_field($table, 'machine_name', 'machine_name', $field_spec); + } + } + else { + db_change_field($table, 'machine_name', 'machine_name', $field_spec); + } + } + + // Fix the 'cron' table field. + $field_spec = array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => 'builtin', + ); + if (!db_field_exists('backup_migrate_schedules', 'cron')) { + try { + db_add_field('backup_migrate_schedules', 'cron', $field_spec); + } + catch (\Exception $e) { + db_change_field('backup_migrate_schedules', 'cron', 'cron', $field_spec); + } + } + else { + db_change_field('backup_migrate_schedules', 'cron', 'cron', $field_spec); + } +} + +/** + * Leave a message to explain the mixup over the backup option. + */ +function backup_migrate_update_7306() { + drupal_set_message(t('Please note that release 7.x-3.4 had a bug which caused all backups to be overwritten instead of having a timestamp added. Please review all backup settings to ensure they work as intended.'), 'warning'); +} + +/** + * - 'backup_migrate_backup_memory_limit' vs 'backup_migrate_memory_limit'. + */ +function backup_migrate_update_7307() { + $limit = variable_get('backup_migrate_backup_memory_limit'); + if (!empty($limit)) { + variable_set('backup_migrate_memory_limit', $limit); + variable_del('backup_migrate_backup_memory_limit'); + } +} + +/** + * Update profiles table filter field to accommodate larger serialized strings. + */ +function backup_migrate_update_7308() { + db_change_field('backup_migrate_profiles', 'filters', 'filters', array( + 'description' => 'The filter settings for the profile.', + 'type' => 'text', + 'size' => 'medium', + 'not null' => TRUE, + 'serialize' => TRUE, + 'serialized default' => 'a:0:{}', + )); +} + +/** + * NodeSquirrel support has been removed. + */ +function backup_migrate_update_7309() { + require_once dirname(__FILE__) . '/includes/destinations.inc'; + require_once dirname(__FILE__) . '/includes/schedules.inc'; + + foreach (backup_migrate_get_schedules() as $schedule) { + // Look for backups which use NodeSquirrel as its destination. + if ($schedule->destination_id == 'nodesquirrel') { + $name = $schedule->name; + + // If this schedule had a second destination, swap the destinations. + if (!empty($schedule->copy_destination_id)) { + $destination = backup_migrate_get_destination($schedule->copy_destination_id); + $schedule->destination_id = $schedule->copy_destination_id; + $schedule->copy_destination_id = ''; + $schedule->name = $destination->name; + $schedule->save(); + drupal_set_message(t('The backup schedule named "%backup" was renamed and now just backups to %destination.', array('%backup' => $name, '%destination' => $destination->name))); + } + + // Just delete it. + else { + $schedule->delete(); + drupal_set_message(t('The backup schedule named "%backup" as been deleted.', array('%backup' => $name))); + } + } + + // Backups which used NodeSquirrel as the second destination will have the + // second destination disabled. + elseif ($schedule->copy_destination_id == 'nodesquirrel') { + $schedule->copy_destination_id = ''; + $schedule->save(); + drupal_set_message(t('The backup schedule named "%backup" no longer keeps a second backup on NodeSquirrel.', array('%backup' => $schedule->name))); + } + } + + // Clear the cache so that the NodeSquirrel plugin is no longer loaded. + cache_clear_all('*', 'cache', TRUE); + + // Rebuild the menus so that the NodeSquirrel menu item is removed. + variable_set('menu_rebuild_needed', TRUE); + + // @todo Remove the configuration later. + drupal_set_message(t('NodeSquirrel stopped being usable as a backup destination on October 1st, 2019 and ceased operations entirely on November 1st, 2019. Because of this, the NodeSquirrel functionality has been disabled. Please switch to an alternate destination if necessary. Please note that the NodeSquirrel configuration itself has not been removed.', array('!url' => 'https://pantheon.io/nodesquirrel-service-end-life'))); +} + +/** + * Disable e-mail destinations. + */ +function backup_migrate_update_7310() { + $out = ''; + + // Disable scheduled e-mail back-ups to allow users to review their + // destinations first. + // + // Which e-mail destinations exist? + $destinations = db_select('backup_migrate_destinations', 'bmd') + ->fields('bmd', array('machine_name')) + ->condition('subtype', 'email', '=') + ->execute() + ->fetchAllAssoc('machine_name', PDO::FETCH_ASSOC); + $destinations = array_keys($destinations); + + if (!empty($destinations)) { + // Which schedules contain enabled e-mail destinations? + $or = db_or(); + $or->condition('destination_id', $destinations, 'IN'); + $or->condition('copy_destination_id', $destinations, 'IN'); + $schedules_query = db_select('backup_migrate_schedules', 'bms') + ->fields('bms', array('schedule_id', 'name')) + ->condition($or) + ->condition('enabled', 1, '='); + $schedules = $schedules_query->execute()->fetchAllAssoc('schedule_id', PDO::FETCH_ASSOC); + + // Disable the relevant schedules. + if (count($schedules) > 0) { + $email_schedules = array_keys($schedules); + $names = array_column($schedules, 'name'); + $replacements = array('@schedules' => '
  • ' . implode('
  • ', $names) . '
'); + db_update('backup_migrate_schedules') + ->fields(['enabled' => '0']) + ->condition('schedule_id', $email_schedules, 'IN') + ->execute(); + $out .= t('Schedules that back up to e-mail destinations have been disabled. Check that you are using the correct e-mail addresses, then re-enable manually. The following schedules have been disabled: @schedules', $replacements); + } + } + + if (!empty($out)) { + return $out; + } + else { + return t('No destinations were affected by this change.'); + } +} + +/** + * Adjust the default performance settings. + */ +function backup_migrate_update_7311() { + $settings = array( + 'backup_migrate_data_rows_per_query' => array( + 'old' => 1000, + 'new' => 50000, + ), + 'backup_migrate_data_rows_per_line' => array( + 'old' => 30, + 'new' => 10, + ), + 'backup_migrate_data_bytes_per_line' => array( + 'old' => 2000, + 'new' => 2 * 1024, + ), + ); + foreach ($settings as $var_name => $data) { + $stored = variable_get($var_name); + + // Nothing was stored in the database. + if (empty($stored)) { + // Don't do anything, the new default will be picked up automatically. + } + // The old value was smaller than or equal to the new value, or was the + // same as the old default, delete it so that the new default can be picked + // up. + elseif (intval($stored) <= $data['new'] || $stored == $data['old']) { + variable_del($var_name); + drupal_set_message(t('!var setting reset to the new default of !val.', array('!var' => $var_name, '!val' => $data['new']))); + } + } +} diff --git a/sites/all/modules/contrib/admin/backup_migrate/backup_migrate.js b/sites/all/modules/contrib/admin/backup_migrate/backup_migrate.js index fa5a498..9d7c171 100644 --- a/sites/all/modules/contrib/admin/backup_migrate/backup_migrate.js +++ b/sites/all/modules/contrib/admin/backup_migrate/backup_migrate.js @@ -1,59 +1,126 @@ +/** + * @file + * Custom JS for the Backup and Migrate module. + */ (function ($) { -Drupal.backup_migrate = { - callbackURL : "", - autoAttach : function() { - if (Drupal.settings.backup_migrate !== undefined) { - if ($("#edit-save-settings").length && !$("#edit-save-settings").attr("checked")) { - // Disable input and hide its description. - // Set display none instead of using hide(), because hide() doesn't work when parent is hidden. - $('div.backup-migrate-save-options').css('display', 'none'); - } - - $("#edit-save-settings").bind("click", function() { - if (!$("#edit-save-settings").attr("checked")) { - $("div.backup-migrate-save-options").slideUp('slow'); - } - else { - // Save unchecked; enable input. - $("div.backup-migrate-save-options").slideDown('slow'); - } - }); + 'use strict'; - $('select[multiple]').each(function() { - $(this).after( - $('').append( - $('').text(Drupal.settings.backup_migrate.checkboxLinkText).click(function() { - Drupal.backup_migrate.selectToCheckboxes($(this).parents('.form-item').find('select')); + Drupal.behaviors.backupMigrate = { + attach: function (context, settings) { + if (Drupal.settings.backup_migrate !== undefined) { + if (Drupal.settings.backup_migrate.dependents !== undefined) { + var key; + for (key in Drupal.settings.backup_migrate.dependents) { + info = Drupal.settings.backup_migrate.dependents[key]; + var dependent = $('#edit-' + info['dependent']); + for (key in info['dependencies']) { + $('[name="' + key + '"]').each(function () { + var dependentval = info['dependencies'][key]; + var dependency = $(this); + (function (dependent, dependency) { + var checkval = function (inval) { + // Do loose comparisons to support things like "true", "1", + // etc. + if (dependency.attr('type') === 'radio') { + var val = $('[name="' + dependency.attr('name') + '"]:checked').val(); + return val == inval; + } + else if (dependency.attr('type') === 'checkbox') { + return dependency.is(':checked') && inval == dependency.val(); + } + else { + return dependency.val() == inval; + } + return false; + }; + if (!checkval(dependentval)) { + // Hide doesn't work inside collapsed fieldsets. + dependent.css('display', 'none'); + } + dependency.bind('load change click keypress focus', function () { + if (checkval(dependentval)) { + dependent.slideDown(); + } + else { + dependent.slideUp(); + } + }).load(); + })(dependent, dependency); + }); + } + } + for (key in Drupal.settings.backup_migrate.destination_selectors) { + var info = Drupal.settings.backup_migrate.destination_selectors[key]; + (function (info) { + var selector = $('#' + info['destination_selector']); + var copy = $('#' + info['copy']) + var copy_selector = $('#' + info['copy_destination_selector']); + var copy_selector_options = {}; + + // Store a copy of the secondary selector options. + copy_selector.find('optgroup').each(function () { + var label = $(this).attr('label'); + copy_selector_options[label] = []; + $(this).find('option').each(function () { + copy_selector_options[label].push(this); + }); + $(this).remove(); }) - ) - ); + + // Assign an action to the main selector to modify the secondary + // selector. + selector.each(function () { + $(this).bind('load change click keypress focus', function () { + var group = $(this).find('option[value=' + $(this).val() + ']').parents('optgroup').attr('label'); + if (group) { + copy.parent().find('.backup-migrate-destination-copy-label').text(info['labels'][group]); + copy_selector.empty(); + for (var key in copy_selector_options) { + if (key != group) { + copy_selector.append(copy_selector_options[key]); + } + } + } + }).load(); + }); + })(info); + } + + // Add the convert to checkboxes functionality to all multiselects. + $('#backup-migrate-ui-manual-backup-form select[multiple], #backup-migrate-crud-edit-form select[multiple]').each(function () { + var self = this; + $(self).after( + $('').append( + $('' + Drupal.settings.backup_migrate.checkboxLinkText + '').click(function () { + var $select = $(self); + var $checkboxes = $('
').addClass('backup-migrate-tables-checkboxes'); + $('option', $select).each(function (i) { + $checkboxes.append( + $('
').append( + $('').prepend( + $('') + .bind('click change load', function () { + if (this.checked) { + $(this).parent().addClass('checked'); + } + else { + $(this).parent().removeClass('checked'); + } + }).load() + ) + ) + ); + }); + $select.parent().find('.backup-migrate-checkbox-link').remove(); + $select.before($checkboxes); + $select.hide(); + }) + ) + ); + }); } - ); + } } - }, - - selectToCheckboxes : function($select) { - var field_id = $select.attr('id'); - var $checkboxes = $('
').addClass('backup-migrate-tables-checkboxes'); - $('option', $select).each(function(i) { - var self = this; - $box = $('').bind('change click', function() { - $select.find('option[value="'+self.value+'"]').attr('selected', this.checked); - if (this.checked) { - $(this).parent().addClass('checked'); - } - else { - $(this).parent().removeClass('checked'); - } - }).attr('checked', this.selected ? 'checked' : ''); - $checkboxes.append($('
').append($('').prepend($box))); - }); - $select.parent().find('.backup-migrate-checkbox-link').remove(); - $select.before($checkboxes); - $select.hide(); } -} - -$(document).ready(Drupal.backup_migrate.autoAttach); })(jQuery); diff --git a/sites/all/modules/contrib/admin/backup_migrate/backup_migrate.module b/sites/all/modules/contrib/admin/backup_migrate/backup_migrate.module index 5e10a56..b50ef05 100644 --- a/sites/all/modules/contrib/admin/backup_migrate/backup_migrate.module +++ b/sites/all/modules/contrib/admin/backup_migrate/backup_migrate.module @@ -1,110 +1,138 @@ - t('Backup and Migrate makes the task of backing up your Drupal database and migrating data from one Drupal install to another easier. It provides a function to backup the entire database to file or download, and to restore from a previous backup. You can also schedule the backup operation. Compression of backup files is also supported. The database backup files created with this module can be imported into this or any other Drupal installation with the !restorelink, or you can use a database tool such as phpMyAdmin or the mysql command line command.', - array( - '!restorelink' => user_access('restore from backup') ? l(t('restore feature'), BACKUP_MIGRATE_MENU_PATH . '/restore') : t('restore feature'), - '!phpmyadminurl' => 'http://www.phpmyadmin.net' - ) - ) - ), - BACKUP_MIGRATE_MENU_PATH => array( - 'title' => t('Quick Backup Tab'), - 'body' => t('Use this form to run simple manual backups of your database. Visit the !helppage for more help using this module', - array('!helppage' => l(t('help page'), 'admin/help/backup_migrate'))), - 'access arguments' => array('perform backup'), - ), - BACKUP_MIGRATE_MENU_PATH . '/export/advanced' => array( - 'title' => t('Advanced Backup Tab'), - 'body' => t('Use this form to run manual backups of your database with more advanced options. If you have any !profilelink saved you can load those settings. You can save any of the changes you make to these settings as a new settings profile.', - array("!profilelink" => user_access('administer backup and migrate') ? l(t('settings profiles'), BACKUP_MIGRATE_MENU_PATH . '/profile') : t('settings profiles'), '!restorelink' => user_access('restore from backup') ? l(t('restore feature'), BACKUP_MIGRATE_MENU_PATH . '/restore') : t('restore feature'), '!phpmyadminurl' => 'http://www.phpmyadmin.net')), - 'access arguments' => array('perform backup'), - ), - BACKUP_MIGRATE_MENU_PATH . '/restore' => array( - 'title' => t('Restore Tab'), - 'body' => t('Upload a backup and migrate backup file. The restore function will not work with database dumps from other sources such as phpMyAdmin.'), - 'access arguments' => array('restore from backup'), - ), - BACKUP_MIGRATE_MENU_PATH . '/destination' => array( - 'title' => t('Destinations'), - 'body' => t('Destinations are the places you can save your backup files to or them load from.'), - 'more' => t('Files can be saved to a directory on your web server, downloaded to your desktop or emailed to a specified email account. From the Destinations tab you can create, delete and edit destinations or list the files which have already been backed up to the available destinations.'), - 'access arguments' => array('administer backup and migrate'), - ), - BACKUP_MIGRATE_MENU_PATH . '/profile' => array( - 'title' => t('Profiles'), - 'body' => t('Profiles are saved backup settings. Profiles store your table exclusion settings as well as your backup file name, compression and timestamp settings. You can use profiles in !schedulelink and for !manuallink.', - array('!schedulelink' => user_access('administer backup and migrate') ? l(t('schedules'), BACKUP_MIGRATE_MENU_PATH . '/schedule') : t('settings profiles'), '!manuallink' => user_access('perform backups') ? l(t('manual backups'), BACKUP_MIGRATE_MENU_PATH) : t('manual backups'))), - 'more' => t('You can create new profiles using the add profiles tab or by checking the "Save these settings" button on the advanced backup page.'), - 'access arguments' => array('administer backup and migrate'), - ), - BACKUP_MIGRATE_MENU_PATH . '/schedule' => array( - 'title' => t('Scheduling'), - 'body' => t('Automatically backup up your database on a regular schedule using cron.', - array('!cronurl' => 'http://drupal.org/cron')), - 'more' => t('Each schedule will run a maximum of once per cron run, so they will not run more frequently than your cron is configured to run. If you specify a number of backups to keep for a schedule, old backups will be deleted as new ones created. If specifiy a number of files to keep other backup files in that schedule\'s destination will get deleted.'), - 'access arguments' => array('administer backup and migrate'), - ), - ); + $help = NULL; + switch ($section) { + case BACKUP_MIGRATE_MENU_PATH: + $help = t('Use this form to run simple manual backups of your database and files.'); + break; - if (isset($help[$section])) { - return $help[$section]['body']; + case BACKUP_MIGRATE_MENU_PATH . '/export/advanced': + $help = t('Use this form to run manual backups of your database and files with more advanced options. If you have any !profilelink saved you can load those settings. You can save any of the changes you make to these settings as a new settings profile.', array( + "!profilelink" => user_access('administer backup and migrate') ? l(t('settings profiles'), BACKUP_MIGRATE_MENU_PATH . '/profile') : t('settings profiles'), + '!restorelink' => user_access('restore from backup') ? l(t('restore feature'), BACKUP_MIGRATE_MENU_PATH . '/restore') : t('restore feature'), + '!phpmyadminurl' => 'http://www.phpmyadmin.net', + )); + break; + + case BACKUP_MIGRATE_MENU_PATH . '/restore': + $help = t('Upload a backup and migrate backup file. The restore function will not work with database dumps from other sources such as phpMyAdmin.'); + break; + + case BACKUP_MIGRATE_MENU_PATH . '/settings/destination': + $help = t('Destinations are the places you can save your backup files to or them load from.'); + break; + + case BACKUP_MIGRATE_MENU_PATH . '/settings/source': + $help = t('Sources are the things that can be backed up such as database or file directories.'); + break; + + case BACKUP_MIGRATE_MENU_PATH . '/settings/profile': + $help = t('Profiles are saved backup settings. Profiles store your table exclusion settings as well as your backup file name, compression and timestamp settings. You can use profiles in !schedulelink and for !manuallink.', array( + '!schedulelink' => user_access('administer backup and migrate') ? l(t('schedules'), BACKUP_MIGRATE_MENU_PATH . '/schedule') : t('settings profiles'), + '!manuallink' => user_access('perform backups') ? l(t('manual backups'), BACKUP_MIGRATE_MENU_PATH) : t('manual backups'), + )); + break; + + case BACKUP_MIGRATE_MENU_PATH . '/schedule': + $help = t('Automatically backup up your database and files on a regular schedule using cron.', array( + '!cronurl' => 'http://drupal.org/cron', + )); + break; + + case BACKUP_MIGRATE_MENU_PATH . '/settings/import': + $help = t('Import a settings profile, backup schedule, source or destination by pasting the export code into the textarea.', array( + '!cronurl' => 'http://drupal.org/cron', + )); + break; + + case 'admin/advanced_help': + // Help link on module list page (admin/modules/list) or advanced help + // module (admin/advanced_help/backup_migrate), use $arg to see if it + // is for this module. + if ($arg[2] != 'backup_migrate') { + break; + } + // Explicit fall-through. + case 'admin/help#backup_migrate': + // Assemble help page based on all strings above plus more. + $help = t('Backup and Migrate makes the task of backing up your Drupal database and migrating data from one Drupal install to another easier. It provides a function to backup the entire database to file or download, and to restore from a previous backup. You can also schedule the backup operation. Compression of backup files is also supported. The database backup files created with this module can be imported into this or any other Drupal installation with the !restorelink, or you can use a database tool such as phpMyAdmin or the mysql command line command.', array( + '!restorelink' => user_access('restore from backup') ? l(t('restore feature'), BACKUP_MIGRATE_MENU_PATH . '/restore') : t('restore feature'), + '!phpmyadminurl' => 'http://www.phpmyadmin.net', + )); + $help = '

' . $help . '

'; + $help .= backup_migrate_help_section(t('Quick Backup Tab'), BACKUP_MIGRATE_MENU_PATH, NULL, array('perform backup')); + $help .= backup_migrate_help_section(t('Advanced Backup Tab'), BACKUP_MIGRATE_MENU_PATH . '/export/advanced', NULL, array('perform backup')); + $help .= backup_migrate_help_section(t('Restore Tab'), BACKUP_MIGRATE_MENU_PATH . '/restore', NULL, array('restore from backup')); + $help .= backup_migrate_help_section(t('Destinations'), BACKUP_MIGRATE_MENU_PATH . '/settings/destination', t('Files can be saved to a directory on your web server, downloaded to your desktop or emailed to a specified email account. From the Destinations tab you can create, delete and edit destinations or list the files which have already been backed up to the available destinations.'), array('administer backup and migrate')); + $help .= backup_migrate_help_section(t('Sources'), BACKUP_MIGRATE_MENU_PATH . '/settings/source', NULL, array('administer backup and migrate')); + $help .= backup_migrate_help_section(t('Profiles'), BACKUP_MIGRATE_MENU_PATH . '/settings/profile', t('You can create new profiles using the add profiles tab or by checking the "Save these settings" button on the advanced backup page.'), array('administer backup and migrate')); + $help .= backup_migrate_help_section(t('Scheduling'), BACKUP_MIGRATE_MENU_PATH . '/schedule', t("Each schedule will run a maximum of once per cron run, so they will not run more frequently than your cron is configured to run. If you specify a number of backups to keep for a schedule, old backups will be deleted as new ones created. If specifiy a number of files to keep other backup files in that schedule's destination will get deleted."), array('administer backup and migrate')); + $help .= backup_migrate_help_section(t('Importing Settings'), BACKUP_MIGRATE_MENU_PATH . '/settings/import', NULL, array()); + break; } - if ($section == 'admin/help#backup_migrate') { - $out = ""; - foreach ($help as $key => $section) { - if (isset($section['access arguments'])) { - foreach($section['access arguments'] as $access) { - if (!user_access($access)) { - continue 2; - } - } - } - if (@$section['title']) { - if (!is_numeric($key)) { - $section['title'] = l($section['title'], $key); - } - $out .= "

". $section['title'] ."

"; - } - $out .= "

". $section['body'] ."

"; - if (!empty($section['more'])) { - $out .= "

". $section['more'] ."

"; - } - } - return $out; - } + return $help; } /** - * Implementation of hook_menu(). + * Helper function for HOOK_help() to create parts of the help page. + */ +function backup_migrate_help_section($title, $section, $more = NULL, $access_arguments = array()) { + $show_this = TRUE; + $help = ''; + + foreach ($access_arguments as $access) { + $show_this = $show_this && user_access($access); + } + + if ($show_this) { + $help = ''; + $help .= '

' . l($title, $section) . '

'; + $help .= '

' . backup_migrate_help($section, NULL) . '

'; + if ($more !== NULL) { + $help .= '

' . $more . '

'; + } + } + return $help; +} + +/** + * Implements hook_menu(). */ function backup_migrate_menu() { + require_once dirname(__FILE__) . '/includes/crud.inc'; + $items = array(); $items[BACKUP_MIGRATE_MENU_PATH] = array( 'title' => 'Backup and Migrate', - 'description' => 'Backup/restore your database or migrate data to or from another Drupal site.', + 'description' => 'Backup/restore your database and files or migrate data to or from another Drupal site.', 'page callback' => 'backup_migrate_menu_callback', 'page arguments' => array('', 'backup_migrate_ui_manual_backup_quick', TRUE), 'access arguments' => array('access backup and migrate'), @@ -124,7 +152,7 @@ function backup_migrate_menu() { 'description' => 'Backup the database.', 'page callback' => 'backup_migrate_menu_callback', 'page arguments' => array('', 'backup_migrate_ui_manual_backup_quick', TRUE), - 'access arguments' => array('access backup and migrate'), + 'access arguments' => array('perform backup'), 'weight' => 0, 'type' => MENU_DEFAULT_LOCAL_TASK, ); @@ -132,7 +160,11 @@ function backup_migrate_menu() { 'title' => 'Advanced Backup', 'description' => 'Backup the database.', 'page callback' => 'backup_migrate_menu_callback', - 'page arguments' => array('', 'backup_migrate_ui_manual_backup_advanced', TRUE), + 'page arguments' => array( + '', + 'backup_migrate_ui_manual_backup_advanced', + TRUE, + ), 'access arguments' => array('perform backup'), 'weight' => 1, 'type' => MENU_LOCAL_TASK, @@ -146,90 +178,350 @@ function backup_migrate_menu() { 'weight' => 1, 'type' => MENU_LOCAL_TASK, ); + $items[BACKUP_MIGRATE_MENU_PATH . '/backups'] = array( + 'title' => 'Saved Backups', + 'description' => 'Previously created backups', + 'page callback' => 'backup_migrate_menu_callback', + 'page arguments' => array('', 'backup_migrate_ui_saved_backups', TRUE), + 'access arguments' => array('access backup files'), + 'weight' => 2, + 'type' => MENU_LOCAL_TASK, + ); + // @todo This was removed because it was getting far too complicated for an + // abstraction. Exposing the actual schedules is more direct. + // @code + // $items[BACKUP_MIGRATE_MENU_PATH . '/schedule'] = array( + // 'title' => 'Schedule', + // 'description' => 'Schedule automatic backups', + // 'page callback' => 'backup_migrate_menu_callback', + // 'page arguments' => array('', 'backup_migrate_ui_schedule', TRUE), + // 'access arguments' => array('administer backup and migrate'), + // 'weight' => 3, + // 'type' => MENU_LOCAL_TASK, + // ); + // @endcode + $items[BACKUP_MIGRATE_MENU_PATH . '/settings'] = array( + 'title' => 'Settings', + 'description' => 'Module settings.', + 'page callback' => 'backup_migrate_menu_callback', + 'page arguments' => array('crud', 'backup_migrate_crud_ui_list_all', TRUE), + 'access arguments' => array('administer backup and migrate'), + 'weight' => 4, + 'type' => MENU_LOCAL_TASK, + ); + $items[BACKUP_MIGRATE_MENU_PATH . '/settings/import'] = array( + 'title' => 'Import', + 'description' => 'Import backup and migrate settings.', + 'page callback' => 'backup_migrate_menu_callback', + 'page arguments' => array('crud', 'backup_migrate_crud_ui_import', TRUE), + 'access arguments' => array('administer backup and migrate'), + 'weight' => 10, + 'type' => MENU_LOCAL_TASK, + ); + $items[BACKUP_MIGRATE_MENU_PATH . '/settings-advanced'] = array( + 'title' => 'Advanced settings', + 'description' => 'Optional settings for Backup and Migrate module.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('backup_migrate_ui_advanced_settings_form'), + 'file' => 'backup_migrate.advanced_settings.inc', + 'access arguments' => array('administer backup and migrate'), + 'weight' => 50, + 'type' => MENU_LOCAL_TASK, + ); - backup_migrate_include('crud'); $items += backup_migrate_crud_menu(); return $items; } /** - * Implementation of hook_cron(). - * - * Takes care of scheduled backups and deletes abandoned temp files. + * Implements hook_element_info(). */ -function backup_migrate_cron() { - // Set the message mode to logging. - _backup_migrate_message_callback('_backup_migrate_message_log'); - - backup_migrate_include('schedules'); - backup_migrate_schedules_run(); - - backup_migrate_include('files'); - _backup_migrate_temp_files_delete(); +function backup_migrate_element_info() { + $types['backup_migrate_dependent'] = array( + '#dependencies' => array(), + '#theme' => 'backup_migrate_dependent', + ); + $types['backup_migrate_file_list'] = array( + '#theme' => 'backup_migrate_file_list', + '#process' => array('form_process_backup_migrate_file_list'), + '#multiple' => FALSE, + '#files' => array(), + '#display_options' => array(), + '#default_value' => 0, + ); + return $types; } /** - * Implementation of hook_permission(). + * Callback function for backup_migrate_file_list '#process' callback. + */ +function form_process_backup_migrate_file_list($element) { + if (count($element['#files']) > 0) { + $weight = 0; + $options = array(); + $max = $count = $element['#display_options']['limit']; + + $files = $element['#files']; + foreach ($files as $id => $file) { + $options[$id] = $file->info('filename'); + } + $element['files'] = array( + '#type' => 'radios', + '#options' => $options, + '#default_value' => $element['#default_value'], + '#parents' => $element['#parents'], + ); + } + + return $element; +} + +/** + * Implements hook_form_update_script_selection_form_alter(). + */ +function backup_migrate_form_update_script_selection_form_alter(&$form, $form_state) { + $msg = t('Before you run any database updates, make sure you have a backup of your database. Use !backup_migrate to create that backup now.', array('!backup_migrate' => l(t('Backup and Migrate'), BACKUP_MIGRATE_MENU_PATH))); + + drupal_set_message($msg, 'warning'); + +} + +/** + * Implements hook_cron(). + */ +function backup_migrate_cron() { + // Optionally disable hook_cron(). + if (variable_get('backup_migrate_disable_cron', BACKUP_MIGRATE_DISABLE_CRON)) { + if (variable_get('backup_migrate_verbose')) { + watchdog('backup_migrate', 'Cron disabled.'); + } + return; + } + + require_once dirname(__FILE__) . '/includes/files.inc'; + require_once dirname(__FILE__) . '/includes/schedules.inc'; + + if (variable_get('backup_migrate_verbose')) { + watchdog('backup_migrate', 'Cron starting.'); + } + + // Takes care of scheduled backups and deletes abandoned temp files. + // Set the message mode to logging. + _backup_migrate_message_callback('_backup_migrate_message_log'); + + // Clean up any previous abandoned tmp files before we attempt to back up. + if (variable_get('backup_migrate_verbose')) { + watchdog('backup_migrate', 'Deleting temp files.'); + } + _backup_migrate_temp_files_delete(); + + if (variable_get('backup_migrate_verbose')) { + watchdog('backup_migrate', 'Processing schedule(s) backups.'); + } + backup_migrate_schedules_cron(); + + // Clean up any temporary files from this run. + if (variable_get('backup_migrate_verbose')) { + watchdog('backup_migrate', 'Deleting temp files.'); + } + _backup_migrate_temp_files_delete(); + + if (variable_get('backup_migrate_verbose')) { + watchdog('backup_migrate', 'Cron finished.'); + } +} + +/** + * Implements hook_cronapi(). + */ +function backup_migrate_cronapi($op, $job = NULL) { + // Optionally disable hook_cron(). + if (variable_get('backup_migrate_disable_cron', BACKUP_MIGRATE_DISABLE_CRON)) { + if (variable_get('backup_migrate_verbose')) { + watchdog('backup_migrate', 'Cron disabled.'); + } + return; + } + + require_once dirname(__FILE__) . '/includes/schedules.inc'; + + $items = array(); + foreach (backup_migrate_get_schedules() as $schedule) { + if ($schedule->get('cron') == BACKUP_MIGRATE_CRON_ELYSIA) { + $id = $schedule->get('id'); + $items['backup_migrate_' . $id] = array( + 'description' => t("Run the Backup and Migrate '!name' schedule", array('!name' => $schedule->get('name'))), + 'rule' => $schedule->get('cron_schedule'), + 'callback' => 'backup_migrate_schedule_run', + 'arguments' => array($id), + 'file' => 'includes/schedules.inc', + ); + } + } + return $items; +} + +/** + * Implements hook_permission(). */ function backup_migrate_permission() { return array( 'access backup and migrate' => array( 'title' => t('Access Backup and Migrate'), 'description' => t('Access the Backup and Migrate admin section.'), + 'restrict access' => TRUE, ), 'perform backup' => array( 'title' => t('Perform a backup'), 'description' => t('Back up any of the available databases.'), + 'restrict access' => TRUE, ), 'access backup files' => array( 'title' => t('Access backup files'), 'description' => t('Access and download the previously created backup files.'), + 'restrict access' => TRUE, ), 'delete backup files' => array( 'title' => t('Delete backup files'), 'description' => t('Delete the previously created backup files.'), + 'restrict access' => TRUE, ), 'restore from backup' => array( 'title' => t('Restore the site'), - 'description' => t('Restore the site\'s database from a backup file.'), + 'description' => t("Restore the site's database from a backup file."), + 'restrict access' => TRUE, ), 'administer backup and migrate' => array( 'title' => t('Administer Backup and Migrate'), 'description' => t('Edit Backup and Migrate profiles, schedules and destinations.'), + 'restrict access' => TRUE, ), ); } /** - * Implementation of hook_simpletest(). + * Implements hook_simpletest(). */ function backup_migrate_simpletest() { - $dir = drupal_get_path('module', 'backup_migrate') .'/tests'; + $dir = drupal_get_path('module', 'backup_migrate') . '/tests'; $tests = file_scan_directory($dir, '\.test$'); return array_keys($tests); } /** - * Implementation of hook_theme(). + * Implements hook_theme(). */ function backup_migrate_theme() { $themes = array( - 'backup_migrate_ui_manual_quick_backup_form' => array( + 'backup_migrate_ui_manual_quick_backup_form_inline' => array( 'arguments' => array('form'), 'render element' => 'form', ), + 'backup_migrate_ui_quick_schedule_form_inline' => array( + 'arguments' => array('form'), + 'render element' => 'form', + ), + 'backup_migrate_dependent' => array( + 'arguments' => array('element'), + 'render element' => 'element', + ), + 'backup_migrate_file_list' => array( + 'arguments' => array('element'), + 'render element' => 'element', + ), + 'backup_migrate_group' => array( + 'variables' => array('title' => '', 'body' => '', 'description' => ''), + ), ); return $themes; } -/* Menu Callbacks */ +/** + * Implements hook_block_info(). + */ +function backup_migrate_block_info() { + $blocks = array(); + $blocks['quick_backup'] = array( + 'info' => t('Backup and Migrate Quick Backup'), + 'admin' => TRUE, + ); + return $blocks; +} + +/** + * Implements hook_block_view(). + */ +function backup_migrate_block_view($delta) { + $function = '_backup_migrate_block_view_' . $delta; + if (function_exists('_backup_migrate_block_view_' . $delta)) { + return $function(); + } +} + +/** + * Quick Backup block. + */ +function _backup_migrate_block_view_quick_backup() { + if (user_access('access backup and migrate') && user_access('perform backup') && $_GET['q'] != BACKUP_MIGRATE_MENU_PATH) { + drupal_add_css(drupal_get_path('module', 'backup_migrate') . '/backup_migrate.css'); + + // Messages should be sent to the browser. + _backup_migrate_message_callback('_backup_migrate_message_browser'); + return array( + 'subject' => t('Quick Backup'), + 'content' => drupal_get_form('backup_migrate_ui_manual_quick_backup_form', FALSE), + ); + } + return array(); +} + +/** + * Implements hook_backup_migrate_destinations(). + */ +function backup_migrate_backup_migrate_schedules() { + require_once dirname(__FILE__) . '/includes/sources.inc'; + + $schedules = array(); + + // Declare a default backup for each source that can be enabled. + // @todo This makes setting up scheduled easier, but only when combined with + // the now disabled quick-schedule feature. + // @code + // $sources = backup_migrate_get_sources(); + // foreach ($sources as $id => $source) { + // $schedule = array( + // 'schedule_id' => $id, + // 'name' => $source->get('name'), + // 'source_id' => $id, + // 'destination_id' => 'scheduled', + // 'profile_id' => 'default', + // 'period' => variable_get('backup_migrate_default_schedule', 60*60), + // 'keep' => BACKUP_MIGRATE_SMART_DELETE, + // 'enabled' => FALSE, + // ); + // $schedules[$id] = backup_migrate_crud_create_item('schedule', $schedule); + // } + // @endcode + + return $schedules; +} + +/** + * Implements hook_ctools_plugin_api(). + */ +function backup_migrate_ctools_plugin_api($owner, $api) { + // Tell CTools that we support the default_mymodule_presets API. + if ($owner == 'backup_migrate') { + return array('version' => 1); + } +} /** * A menu callback helper. Handles file includes and interactivity setting. */ function backup_migrate_menu_callback($include, $function, $interactive = TRUE) { if ($include) { - backup_migrate_include($include); + require_once dirname(__FILE__) . '/includes/' . $include . '.inc'; } // Set the message handler based on interactivity setting. _backup_migrate_message_callback($interactive ? '_backup_migrate_message_browser' : '_backup_migrate_message_log'); @@ -240,18 +532,28 @@ function backup_migrate_menu_callback($include, $function, $interactive = TRUE) /** * Include views .inc files as necessary. + * + * @deprecated */ function backup_migrate_include() { static $used = array(); foreach (func_get_args() as $file) { if (!isset($used[$file])) { - require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'backup_migrate') . "/includes/$file.inc"; + require_once backup_migrate_include_path($file); } - + $used[$file] = TRUE; } } +/** + * Include .inc files as necessary. + * + * @deprecated + */ +function backup_migrate_include_path($file) { + return DRUPAL_ROOT . '/' . drupal_get_path('module', 'backup_migrate') . "/includes/$file.inc"; +} /** * The menu callback for easy manual backups. @@ -271,7 +573,8 @@ function backup_migrate_ui_manual_backup_quick() { * The menu callback for advanced manual backups. */ function backup_migrate_ui_manual_backup_advanced() { - backup_migrate_include('profiles'); + require_once dirname(__FILE__) . '/includes/profiles.inc'; + $out = array(); $profile_id = arg(BACKUP_MIGRATE_MENU_DEPTH + 2); $profile = _backup_migrate_profile_saved_default_profile($profile_id); @@ -325,15 +628,185 @@ function backup_migrate_ui_manual_backup_load_profile_form_submit($form, &$form_ } } +/** + * The menu callback for quick schedules. + */ +function backup_migrate_ui_schedule() { + require_once dirname(__FILE__) . '/includes/sources.inc'; + require_once dirname(__FILE__) . '/includes/schedules.inc'; + require_once dirname(__FILE__) . '/includes/profiles.inc'; + + $sources = backup_migrate_get_sources(); + $schedules = backup_migrate_get_schedules(); + + $out = drupal_get_form('backup_migrate_ui_schedule_form', $sources, $schedules); + return $out; +} + +/** + * The quick schedule form. + */ +function backup_migrate_ui_schedule_form($form, &$form_state, $sources, $schedules) { + $form['quickschedule'] = array('#tree' => TRUE); + + // Add a quick schedule item for each source. + foreach ($sources as $id => $source) { + if (isset($schedules[$id])) { + $schedule = $schedules[$id]; + $key = preg_replace('/[^A-Za-z0-9\-_]/', '-', $id); + $form['quickschedule'][$key] = array(); + $form['quickschedule'][$key]['#theme'] = 'backup_migrate_ui_quick_schedule_form_inline'; + + $form['quickschedule'][$key]['id'] = array( + '#type' => 'value', + '#value' => $id, + ); + + $form['quickschedule'][$key]['enabled'] = array( + '#type' => 'checkbox', + '#title' => t('Automatically backup my @source', array('@source' => $source->get('name'))), + '#default_value' => $schedule->get('enabled'), + ); + + // $form['quickschedule'][$id]['source_id'] = array( + // '#type' => 'item', + // '#title' => t('Backup Source'), + // '#value' => $source->get('name'), + // ); + $form['quickschedule'][$key]['settings'] = array( + '#type' => 'backup_migrate_dependent', + '#dependencies' => array( + 'quickschedule[' . $key . '][enabled]' => TRUE, + ), + ); + $options = array( + (60 * 60) => t('Once an hour'), + (60 * 60 * 24) => t('Once a day'), + (60 * 60 * 24 * 7) => t('Once a week'), + ); + $period = $schedule->get('period'); + if (!isset($options[$period])) { + $options[$period] = $schedule->get('frequency_description'); + } + $form['quickschedule'][$key]['settings']['period'] = array( + '#type' => 'select', + '#title' => t('Schedule Frequency'), + '#options' => $options, + '#default_value' => $period, + ); + $form['quickschedule'][$key]['settings']['destination'] = _backup_migrate_get_destination_pulldown('scheduled backup', $schedule->get('destination_id'), $schedule->get('copy_destination_id')); + + $form['quickschedule'][$key]['settings']['destination']['destination_id']['#parents'] = array( + 'quickschedule', + $key, + 'destination_id', + ); + $form['quickschedule'][$key]['settings']['destination']['copy']['#parents'] = array( + 'quickschedule', + $key, + 'copy', + ); + $form['quickschedule'][$key]['settings']['destination']['copy_destination']['copy_destination_id']['#parents'] = array( + 'quickschedule', + $key, + 'copy_destination_id', + ); + + array( + '#type' => 'select', + '#title' => t('Backup Destination'), + '#options' => _backup_migrate_get_destination_form_item_options('scheduled backup'), + '#default_value' => $schedule->get('period'), + ); + $form['quickschedule'][$key]['settings']['profile_id'] = array( + '#type' => 'select', + '#title' => t('Settings Profile'), + '#default_value' => $schedule->get('profile_id'), + '#options' => _backup_migrate_get_profile_form_item_options(), + ); + + $keep = $schedule->get('keep'); + $option_keys = array_unique(array( + BACKUP_MIGRATE_SMART_DELETE, + 30, + 100, + 500, + BACKUP_MIGRATE_KEEP_ALL, + $keep, + )); + $options = array(); + foreach ($option_keys as $i) { + $options[$i] = $schedule->generate_keep_description($i); + } + $form['quickschedule'][$key]['settings']['keep'] = array( + '#type' => 'select', + '#title' => t('Backup retention'), + '#options' => $options, + '#default_value' => $keep, + ); + $form['quickschedule'][$key]['settings']['advanced'] = array( + '#type' => 'markup', + '#markup' => l(t('Advanced Settings.'), BACKUP_MIGRATE_MENU_PATH . '/settings/schedule/edit/' . $id, array('query' => drupal_get_destination())), + ); + // Set the parent for the setttings up a level. + foreach (element_children($form['quickschedule'][$key]['settings']) as $child) { + $form['quickschedule'][$key]['settings'][$child]['#parents'] = array( + 'quickschedule', + $key, + $child, + ); + } + } + } + if (element_children($form['quickschedule'])) { + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Save Schedules'), + ); + } + $form['advanced'] = array( + '#type' => 'markup', + '#markup' => '', + ); + return $form; +} + +/** + * Submit the quick schedule form. + */ +function backup_migrate_ui_schedule_form_submit($form, &$form_state) { + require_once dirname(__FILE__) . '/includes/schedules.inc'; + + if (user_access('adminsiter backup and migrate')) { + // Override the backups. + foreach ($form_state['values']['quickschedule'] as $key => $values) { + $id = $values['id']; + if ($schedule = backup_migrate_get_schedule($id)) { + $schedule->from_array($values); + $schedule->save(); + } + } + drupal_set_message(t('Your schedules have been saved')); + } +} + /** * The quick backup form. */ -function backup_migrate_ui_manual_quick_backup_form($form, &$form_state) { - backup_migrate_include('profiles', 'destinations'); - drupal_add_js(drupal_get_path('module', 'backup_migrate') .'/backup_migrate.js'); +function backup_migrate_ui_manual_quick_backup_form($form, &$form_state, $inline = TRUE) { + require_once dirname(__FILE__) . '/includes/profiles.inc'; + require_once dirname(__FILE__) . '/includes/destinations.inc'; + require_once dirname(__FILE__) . '/includes/sources.inc'; + + drupal_add_js(drupal_get_path('module', 'backup_migrate') . '/backup_migrate.js'); $form = array(); + // Theme the form if we want it inline. + if ($inline) { + $form['#theme'] = 'backup_migrate_ui_manual_quick_backup_form_inline'; + } + $form['quickbackup'] = array( '#type' => 'fieldset', "#title" => t("Quick Backup"), @@ -344,12 +817,8 @@ function backup_migrate_ui_manual_quick_backup_form($form, &$form_state) { $form['quickbackup']['source_id'] = _backup_migrate_get_source_pulldown(variable_get('backup_migrate_source_id', NULL)); - $form['quickbackup']['destination_id'] = array( - "#type" => "select", - "#title" => t("Destination"), - "#options" => _backup_migrate_get_destination_form_item_options('manual backup'), - "#default_value" => variable_get("backup_migrate_destination_id", "download"), - ); + $form['quickbackup']['destination'] = _backup_migrate_get_destination_pulldown('manual backup', variable_get('backup_migrate_destination_id', 'download'), variable_get('backup_migrate_copy_destination_id', '')); + $profile_options = _backup_migrate_get_profile_form_item_options(); $form['quickbackup']['profile_id'] = array( "#type" => "select", @@ -358,50 +827,88 @@ function backup_migrate_ui_manual_quick_backup_form($form, &$form_state) { "#options" => $profile_options, ); + $form['quickbackup']['description_enabled'] = array( + '#type' => 'checkbox', + "#title" => t("Add a note to the backup"), + ); + $form['quickbackup']['description'] = array( + '#type' => 'backup_migrate_dependent', + '#dependencies' => array( + 'description_enabled' => TRUE, + ), + ); + + $form['quickbackup']['description']['description'] = array( + '#type' => 'textarea', + "#title" => t("Note"), + '#description' => t('This note will be saved with the backup file and shown on the listing page.'), + ); + + $form['advanced'] = array( + '#type' => 'markup', + '#markup' => '', + ); + $form['quickbackup']['submit'] = array( '#type' => 'submit', '#value' => t('Backup now'), '#weight' => 1, ); - $form['advanced'] = array( - '#type' => 'markup', - '#markup' => t('For more backup options, try the advanced backup page.', array('!advancedurl' => url(BACKUP_MIGRATE_MENU_PATH . '/export/advanced'))), - ); + $form['#validate'] = array('backup_migrate_ui_manual_quick_backup_form_validate'); + $form['#submit'] = array('backup_migrate_ui_manual_quick_backup_form_submit'); - - return $form; + return _backup_migrate_ui_action_form($form, $form_state, 'backup'); } /** * Validate the quick backup form. */ function backup_migrate_ui_manual_quick_backup_form_validate($form, &$form_state) { + require_once dirname(__FILE__) . '/includes/profiles.inc'; + require_once dirname(__FILE__) . '/includes/destinations.inc'; + if ($form_state['values']['source_id'] == $form_state['values']['destination_id']) { form_set_error('destination_id', t('A source cannot be backed up to itself. Please pick a different destination for this backup.')); } + // Wrap profile_id for prevent Undefined index notice. + $profile_id = isset($form_state['values']['profile_id']) ? $form_state['values']['profile_id'] : NULL; + + // For a quick backup use the default settings. + $settings = _backup_migrate_profile_saved_default_profile($profile_id); + + // Set the destination to the one chosen in the pulldown. + $settings->destination_id = array($form_state['values']['destination_id']); + $settings->source_id = $form_state['values']['source_id']; + + // Add the second destination. + if (!empty($form_state['values']['copy_destination_id'])) { + $settings->destination_id[] = $form_state['values']['copy_destination_id']; + } + + // Add the note. + if (!empty($form_state['values']['description_enabled'])) { + $settings->filters['utils_description'] = $form_state['values']['description']; + } + $form_state['values']['settings'] = $settings; } /** * Submit the quick backup form. */ function backup_migrate_ui_manual_quick_backup_form_submit($form, &$form_state) { - backup_migrate_include('profiles', 'destinations'); - if (user_access('perform backup')) { - // For a quick backup use the default settings. - $settings = _backup_migrate_profile_saved_default_profile($form_state['values']['profile_id']); - - // Set the destination to the one chosen in the pulldown. - $settings->destination_id = $form_state['values']['destination_id']; - $settings->source_id = $form_state['values']['source_id']; + require_once dirname(__FILE__) . '/includes/destinations.inc'; + require_once dirname(__FILE__) . '/includes/profiles.inc'; + if (user_access('perform backup') && !empty($form_state['values']['settings'])) { // Save the settings for next time. variable_set("backup_migrate_source_id", $form_state['values']['source_id']); variable_set("backup_migrate_destination_id", $form_state['values']['destination_id']); + variable_set("backup_migrate_copy_destination_id", $form_state['values']['copy_destination_id']); variable_set("backup_migrate_profile_id", $form_state['values']['profile_id']); // Do the backup. - backup_migrate_ui_manual_backup_perform($settings); + backup_migrate_ui_manual_backup_perform($form_state['values']['settings']); } $form_state['redirect'] = BACKUP_MIGRATE_MENU_PATH; } @@ -409,49 +916,172 @@ function backup_migrate_ui_manual_quick_backup_form_submit($form, &$form_state) /** * Theme the quick backup form. */ -function theme_backup_migrate_ui_manual_quick_backup_form($form) { +function theme_backup_migrate_ui_manual_quick_backup_form_inline($form) { + drupal_add_css(drupal_get_path('module', 'backup_migrate') . '/backup_migrate.css'); + $form = $form['form']; // Remove the titles so that the pulldowns can be displayed inline. unset($form['quickbackup']['source_id']['#title']); - unset($form['quickbackup']['destination_id']['#title']); + unset($form['quickbackup']['destination']['destination_id']['#title']); + unset($form['quickbackup']['destination']['destination_id']['#description']); unset($form['quickbackup']['profile_id']['#title']); + unset($form['quickbackup']['destination']['copy_destination']['copy_destination_id']['#title']); $replacements = array( '!from' => drupal_render($form['quickbackup']['source_id']), - '!to' => drupal_render($form['quickbackup']['destination_id']), + '!to' => drupal_render($form['quickbackup']['destination']['destination_id']), '!profile' => drupal_render($form['quickbackup']['profile_id']), - '!submit' => drupal_render($form['quickbackup']['submit']), + // '!submit' => drupal_render($form['quickbackup']['submit']), ); $form['quickbackup']['markup'] = array( '#type' => 'markup', - "#prefix" => '
', + "#prefix" => '
', "#suffix" => '
', - '#markup' => t('Backup from !from to !to using !profile !submit', $replacements), + '#markup' => t('Backup my !from to !to using !profile', $replacements), ); + + $replacements = array( + '!to' => drupal_render($form['quickbackup']['destination']['copy_destination']['copy_destination_id']), + ); + $form['quickbackup']['destination']['copy']["#prefix"] = '
'; + $form['quickbackup']['destination']['copy']["#suffix"] = $replacements['!to'] . '
'; + // This is not good translation practice as it relies on the structure of + // English. If I add the pulldown to the label, howerver, the box toggles + // when the pulldown is clicked. + // $form['quickbackup']['destination']['copy']['#title'] = t('Save a copy to');. unset($form['quickbackup']['source_id']); - unset($form['quickbackup']['destination_id']); + unset($form['quickbackup']['destination']['destination_id']); + unset($form['quickbackup']['destination']['copy_destination']); unset($form['quickbackup']['profile_id']); - unset($form['quickbackup']['submit']); + // unset($form['quickbackup']['submit']);. + return drupal_render_children($form); +} + +/** + * Theme the quick schedule form. + */ +function theme_backup_migrate_ui_quick_schedule_form_inline($form) { + drupal_add_js(drupal_get_path('module', 'backup_migrate') . '/backup_migrate.js'); + drupal_add_css(drupal_get_path('module', 'backup_migrate') . '/backup_migrate.css'); + + $form = $form['form']; + + // Remove the titles so that the pulldowns can be displayed inline. + unset($form['settings']['source_id']['#title']); + unset($form['settings']['period']['#title']); + unset($form['settings']['destination']['destination_id']['#title']); + unset($form['settings']['destination']['destination_id']['#description']); + unset($form['settings']['profile_id']['#title']); + unset($form['settings']['destination']['copy_destination']['copy_destination_id']['#title']); + unset($form['settings']['profile_id']['#title']); + unset($form['settings']['keep']['#title']); + unset($form['settings']['keep']['#field_prefix']); + unset($form['settings']['keep']['#field_suffix']); + + $replacements = array( + '!from' => drupal_render($form['settings']['source_id']), + '!to' => drupal_render($form['settings']['destination']['destination_id']), + '!frequency' => drupal_render($form['settings']['period']), + '!profile' => drupal_render($form['settings']['profile_id']), + '!keep' => drupal_render($form['settings']['keep']), + ); + $form['settings'] = array( + 'inline' => array( + '#type' => 'markup', + "#prefix" => '
', + "#suffix" => '
', + '#markup' => t('Backup !frequency to !to using !profile keeping !keep', $replacements), + ), + ) + $form['settings']; + + $replacements = array( + '!to' => drupal_render($form['settings']['destination']['copy_destination']['copy_destination_id']), + ); + $form['settings']['destination']['copy']["#prefix"] = '
'; + $form['settings']['destination']['copy']["#suffix"] = $replacements['!to'] . '
'; + + unset($form['settings']['period']); + unset($form['settings']['destination']['destination_id']); + unset($form['settings']['destination']['copy_destination']); + unset($form['settings']['profile_id']); + unset($form['settings']['keep']); return drupal_render_children($form); } +/** + * Theme the dependent form section. + */ +function theme_backup_migrate_dependent($vars) { + $element = $vars['element']; + drupal_add_js(drupal_get_path('module', 'backup_migrate') . '/backup_migrate.js'); + drupal_add_css(drupal_get_path('module', 'backup_migrate') . '/backup_migrate.css'); + + $id = drupal_html_id(implode('-', $element['#array_parents'])); + + $settings = array( + 'backup_migrate' => array( + 'dependents' => array($id => array('dependent' => $id, 'dependencies' => $element['#dependencies'])), + ), + ); + drupal_add_js($settings, 'setting'); + + return '
' . drupal_render_children($element) . '
'; +} + +/** + * Theme file list form widget. + */ +function theme_backup_migrate_file_list($vars) { + $element = $vars['element']; + + // Render the radios so they can be placed in the file list rendering. + $vars['element']['#display_options']['form_elements'] = array(); + if (isset($vars['element']['files']) && count($vars['element']['files'])) { + foreach (element_children($vars['element']['files']) as $id) { + $vars['element']['files'][$id]['#title'] = ''; + $vars['element']['#display_options']['form_elements'][$id] = render($vars['element']['files'][$id]); + } + } + + $out = _backup_migrate_ui_destination_display_file_list($vars['element']['#files'], $vars['element']['#display_options']); + return $out; +} + +/** + * Theme the dependent form section. + */ +function theme_backup_migrate_group($vars) { + $output = ''; + if (!empty($vars['title'])) { + $output .= '

' . $vars['title'] . '

'; + } + if (!empty($vars['description'])) { + $output .= '
' . $vars['description'] . '
'; + } + $output .= '
' . $vars['body'] . '
'; + $output = '
' . $output . '
'; + return $output; +} + /** * The backup/export form. */ function backup_migrate_ui_manual_backup_form($form, &$form_state, $profile) { - drupal_add_js(drupal_get_path('module', 'backup_migrate') .'/backup_migrate.js', array('type' => 'module', 'scope' => 'footer')); + drupal_add_js(drupal_get_path('module', 'backup_migrate') . '/backup_migrate.js', array('type' => 'module', 'scope' => 'footer')); $form = array(); $form += _backup_migrate_get_source_form('db'); - $form += _backup_migrate_ui_backup_settings_form($profile); - $form['profile_id'] = array( + $form_state['profile'] = $profile; + + $form['machine_name'] = array( "#type" => "value", '#default_value' => $profile->get_id(), ); + $form['storage'] = array( "#type" => "value", '#default_value' => $profile->storage, @@ -463,18 +1093,25 @@ function backup_migrate_ui_manual_backup_form($form, &$form_state, $profile) { "#collapsed" => FALSE, "#tree" => FALSE, "#description" => t("Choose where the backup file will be saved. Backup files contain sensitive data, so be careful where you save them. Select 'Download' to download the file to your desktop."), - '#weight' => 70, + // '#weight' => 70, ); - $form['destination']['destination_id'] = array( - "#type" => "select", - "#title" => t("Destination"), - "#options" => _backup_migrate_get_destination_form_item_options('manual backup'), - "#default_value" => variable_get("backup_migrate_destination_id", "download"), - ); - if (user_access('administer backup and migrate')) { - $form['destination']['destination_id']['#description'] = l(t("Create new destination"), BACKUP_MIGRATE_MENU_PATH . "/destination/add"); - } + $form['destination']['destination'] = _backup_migrate_get_destination_pulldown('manual backup', variable_get('backup_migrate_destination_id', 'download')); + $form += _backup_migrate_ui_backup_settings_form($profile); + + // Make the source settings dependent on the source selected. Except don't + // because it looks super weird. + // @code + // foreach (element_children($form['sources']) as $source) { + // $form['sources'][$source] = array( + // '#type' => 'backup_migrate_dependent', + // '#dependencies' => array( + // 'source_id' => $source, + // ), + // 'settings' => $form['sources'][$source] + // ); + // } + // @endcode if (user_access('administer backup and migrate')) { $form['save_settings'] = array( "#type" => "checkbox", @@ -483,8 +1120,10 @@ function backup_migrate_ui_manual_backup_form($form, &$form_state, $profile) { '#weight' => 80, ); $form['save_options'] = array( - '#prefix' => '
', - '#suffix' => '
', + '#type' => 'backup_migrate_dependent', + '#dependencies' => array( + 'save_settings' => TRUE, + ), '#weight' => 90, ); $name = array( @@ -521,24 +1160,23 @@ function backup_migrate_ui_manual_backup_form($form, &$form_state, $profile) { $form['#validate'][] = 'backup_migrate_ui_manual_quick_backup_form_validate'; $form['#submit'][] = 'backup_migrate_ui_manual_backup_form_submit'; - $form[] = array( + $form['submit'] = array( '#type' => 'submit', '#value' => t('Backup now'), '#weight' => 100, ); - return $form; + return _backup_migrate_ui_action_form($form, $form_state, 'backup'); } - /** - * Submit the form. Save the values as defaults if desired and output the backup file. + * Submits the form. + * + * Save the values as defaults if desired and output the backup file. */ function backup_migrate_ui_manual_backup_form_submit($form, &$form_state) { - // Save the settings profile if the save box is checked. -// $form_state['values']['nodata_tables'] = array_filter((array)$form_state['values']['nodata_tables']); -// $form_state['values']['exclude_tables'] = array_filter((array)$form_state['values']['exclude_tables']); - - $profile = backup_migrate_crud_create_item('profile', $form_state['values']); + // Create a profile with the given settings. + $profile = $form_state['profile']; + $profile->from_array($form_state['values']); // Save the settings profile if the save box is checked. if ($form_state['values']['save_settings'] && user_access('administer backup and migrate')) { @@ -553,16 +1191,111 @@ function backup_migrate_ui_manual_backup_form_submit($form, &$form_state) { } // Perform the actual backup if that is what was selected. - if ($form_state['values']['op'] == t('Backup now') && user_access('perform backup')) { + if ($form_state['values']['op'] !== t('Save Without Backing Up') && user_access('perform backup')) { backup_migrate_ui_manual_backup_perform($profile); } $form_state['redirect'] = BACKUP_MIGRATE_MENU_PATH . "/export/advanced"; } +/** + * Alters a B&M action (backup/restore) form to allow for filters to add steps. + */ +function _backup_migrate_ui_action_form($form, &$form_state, $op = 'backup') { + $form['operation'] = array('#type' => 'value', '#value' => $op); + + // If the form has been submitted at least once then see if there are + // additional form elements to add. + if (!empty($form_state['storage']['values']) && !empty($form_state['values']['settings'])) { + $page = _backup_migrate_filter_before_action_form($form_state['values']['settings'], $op); + if (!empty($page)) { + $page['#validate'] = $form['#validate']; + $page['#submit'] = $form['#submit']; + $page['submit'] = array( + '#type' => 'submit', + '#value' => isset($form_state['values']['op']) ? check_plain($form_state['values']['op']) : t('Continue'), + '#weight' => 1, + ); + $form = $page; + } + } + + // Add some pre and post processing functions to validate and submit. + array_unshift($form['#validate'], 'backup_migrate_ui_action_form_pre_validate'); + array_push($form['#validate'], 'backup_migrate_ui_action_form_post_validate'); + + array_unshift($form['#submit'], 'backup_migrate_ui_action_form_pre_submit'); + + return $form; +} + +/** + * Allows filters, sources and destinations to present additional form elements. + * + * The additional form elements will be shown to the user before an action is + * submitted. + */ +function backup_migrate_ui_action_form_pre_validate($form, &$form_state) { + if (!empty($form_state['storage']['values'])) { + $form_state['values'] += $form_state['storage']['values']; + } +} + +/** + * Allows filters, sources and destinations to present additional form elements. + * + * The additional form elements will be shown to the user before an action is + * submitted. + */ +function backup_migrate_ui_action_form_post_validate($form, &$form_state) { + require_once dirname(__FILE__) . '/includes/filters.inc'; + + if (!empty($form_state['values']['settings'])) { + // See if there are extra form items to be processed. + $form = _backup_migrate_filter_before_action_form($form_state['values']['settings'], $form_state['values']['operation']); + if ($form) { + // First time through the form simply rebuild the form to show the + // additional fields. + if (empty($form_state['storage']['values'])) { + $form_state['storage']['values'] = $form_state['values']; + $form_state['rebuild'] = TRUE; + } + else { + backup_migrate_filters_before_action_form_validate($form_state['values']['settings'], $form_state['values']['operation'], $form, $form_state); + } + } + } +} + +/** + * Allows filters, sources and destinations to present additional form elements. + * + * The additional form elements will be shown to the user before an action is + * submitted. + */ +function backup_migrate_ui_action_form_pre_submit($form, &$form_state) { + if (!empty($form_state['storage']['values']) && !empty($form_state['values']['settings'])) { + // Check if there are any form elements to submit. + backup_migrate_filters_before_action_form_submit($form_state['values']['settings'], $form_state['values']['operation'], $form, $form_state); + // Clear the storage so the form can continue as normal. + unset($form_state['storage']); + } +} + +/** + * Retrieves form items to be filled out before an action can continue. + * + * @return array + * An array of form items. + */ +function _backup_migrate_filter_before_action_form($settings, $op) { + $form = array(); + $form = backup_migrate_filters_before_action_form($settings, $op); + return $form; +} + /** * Perform an actual manual backup and tell the user of the progress. */ - function backup_migrate_ui_manual_backup_perform($settings) { // Peform the actual backup. backup_migrate_perform_backup($settings); @@ -579,17 +1312,73 @@ function backup_migrate_ui_manual_restore() { * The restore/import upload form. */ function backup_migrate_ui_manual_restore_form() { - backup_migrate_include('filters', 'destinations'); + require_once dirname(__FILE__) . '/includes/destinations.inc'; + require_once dirname(__FILE__) . '/includes/filters.inc'; + require_once dirname(__FILE__) . '/includes/sources.inc'; + + drupal_set_message(t('Restoring will delete some or all of your data and cannot be undone. Always test your backups on a non-production server!'), 'warning', FALSE); $form = array(); - $sources = _backup_migrate_get_destination_form_item_options('source'); + $from_default = (isset($_GET['page']) || isset($_GET['sort'])) ? 'saved' : 'upload'; + $form['backup_migrate_restore_from_upload'] = array( + "#type" => "radio", + "#title" => t('Restore from an uploaded file'), + '#return_value' => 'upload', + '#parents' => array('from'), + '#default_value' => $from_default, + ); + + $form['backup_migrate_restore_upload'] = array( + '#type' => 'backup_migrate_dependent', + '#dependencies' => array( + 'from' => 'upload', + ), + ); + + $form['backup_migrate_restore_upload']['backup_migrate_restore_upload'] = array( + '#title' => t('Upload a Backup File'), + '#type' => 'file', + '#description' => t("Upload a backup file created by Backup and Migrate. For other database or file backups please use another tool for import. Max file size: %size", array("%size" => format_size(file_upload_max_size()))), + ); + + $form['backup_migrate_restore_from_saved'] = array( + "#type" => "radio", + "#title" => t('Restore from a saved backup'), + '#return_value' => 'saved', + '#parents' => array('from'), + '#default_value' => $from_default, + ); + + $form['backup_migrate_restore_saved'] = array( + '#type' => 'backup_migrate_dependent', + '#dependencies' => array( + 'from' => 'saved', + ), + ); + + list($files, $fetch_time, $remote) = backup_migrate_get_saved_backups(); + $keys = array_keys($files); + $default = reset($keys); + $form['backup_migrate_restore_saved']['file'] = array( + '#type' => 'backup_migrate_file_list', + '#display_options' => array( + 'limit' => 5, + 'pager' => FALSE, + 'more' => BACKUP_MIGRATE_MENU_PATH . '/backups', + 'operations' => FALSE, + ), + '#default_value' => $default, + '#files' => $files, + ); + + $sources = _backup_migrate_get_source_form_item_options(); if (count($sources) > 1) { $form['source_id'] = array( "#type" => "select", "#title" => t("Restore to"), - "#options" => _backup_migrate_get_destination_form_item_options('source'), - "#description" => t("Choose the database to restore to. Any database destinations you have created and any databases specified in your settings.php can be restored to."), + "#options" => $sources, + "#description" => t("Choose where to restore to. Any database destinations you have created and any databases specified in your settings.php can be restored to."), "#default_value" => 'db', ); } @@ -600,20 +1389,13 @@ function backup_migrate_ui_manual_restore_form() { ); } - $form['backup_migrate_restore_upload'] = array( - '#title' => t('Upload a Backup File'), - '#type' => 'file', - '#description' => t("Upload a backup file created by this version of this module. For other database backups please use another tool for import. Max file size: %size", array("%size" => format_size(file_upload_max_size()))), - ); - drupal_set_message(t('Restoring will delete some or all of your data and cannot be undone. Always test your backups on a non-production server!'), 'warning', FALSE); - $form = array_merge_recursive($form, backup_migrate_filters_settings_form(backup_migrate_filters_settings_default('restore'), 'restore')); // Add the advanced fieldset if there are any fields in it. - if (@$form['advanced']) { + if (isset($form['advanced'])) { $form['advanced']['#type'] = 'fieldset'; $form['advanced']['#title'] = t('Advanced Options'); - $form['advanced']['#collapsed'] = true; - $form['advanced']['#collapsible'] = true; + $form['advanced']['#collapsed'] = TRUE; + $form['advanced']['#collapsible'] = TRUE; } $form['submit'] = array( @@ -623,7 +1405,7 @@ function backup_migrate_ui_manual_restore_form() { if (user_access('access backup files')) { $form[] = array( '#type' => 'markup', - '#markup' => t('

Or you can restore one of the files in your saved backup destinations.

', array("!url" => url(BACKUP_MIGRATE_MENU_PATH . "/destination"))), + '#markup' => t('

Or you can restore one of the files in your saved backup destinations.

', array("!url" => url(BACKUP_MIGRATE_MENU_PATH . '/backups'))), ); } $form['#attributes'] = array('enctype' => 'multipart/form-data'); @@ -634,11 +1416,22 @@ function backup_migrate_ui_manual_restore_form() { * The restore submit. Do the restore. */ function backup_migrate_ui_manual_restore_form_submit($form, &$form_state) { - $validators = array('file_validate_extensions' => array('gz zip sql mysql bz bz2')); - if ($file = file_save_upload('backup_migrate_restore_upload', $validators)) { - backup_migrate_include('destinations'); - backup_migrate_perform_restore('upload', $file->uri, $form_state['values']); + require_once dirname(__FILE__) . '/includes/destinations.inc'; + + $validators = array( + 'file_validate_extensions' => array('gz zip sql mysql bz bz2 encrypt'), + ); + + $values = $form_state['values']; + if ($values['from'] == 'upload' && $file = file_save_upload('backup_migrate_restore_upload', $validators)) { + backup_migrate_perform_restore('upload', $file->uri, $values); } + elseif ($values['from'] == 'saved' && $file = $values['file']) { + list($destination, $file_id) = explode('/', $file, 2); + $file = + backup_migrate_perform_restore($destination, $file_id, $form_state['values']); + } + $form_state['redirect'] = BACKUP_MIGRATE_MENU_PATH . '/restore'; } @@ -648,30 +1441,200 @@ function backup_migrate_ui_manual_restore_form_submit($form, &$form_state) { function backup_migrate_ui_export_form($form, &$form_state, $item) { if ($item && function_exists('ctools_var_export')) { $code = ctools_var_export($item); - $form = ctools_export_form($form_state, $code); + $form = ctools_export_form($form, $form_state, $code); return $form; } return array(); } +/** + * List the previously created backups from across multiple destinations. + */ +function backup_migrate_ui_saved_backups() { + require_once dirname(__FILE__) . '/includes/destinations.inc'; + require_once dirname(__FILE__) . '/includes/profiles.inc'; + require_once dirname(__FILE__) . '/includes/sources.inc'; + + drupal_add_css(drupal_get_path('module', 'backup_migrate') . '/backup_migrate.css'); + + $remote = FALSE; + $now = $fetch_time = time(); + $refresh = isset($_GET['refresh']); + $out = ''; + list($files, $fetch_time, $remote) = backup_migrate_get_saved_backups($refresh); + + // Redirect if refreshing instead of displaying. + if ($refresh) { + drupal_goto($_GET['q']); + } + + $fetch = $out = ''; + + // Get the fetch link. + if ($fetch_time < $now) { + $fetch = '
' . t('This listing was fetched !time ago. !refresh', array( + '!time' => format_interval($now - $fetch_time, 1), + '!refresh' => l(t('fetch now'), $_GET['q'], array( + 'query' => array('refresh' => 'true'), + )), + )) . '
'; + } + + // Render the list. + $out .= $fetch; + $out .= _backup_migrate_ui_destination_display_file_list($files, array('limit' => 20, 'pager' => TRUE)); + $out .= $fetch; + + // Add a remote backups warning if there are no remote backups. + if (!$remote && user_access('administer backup and migrate')) { + drupal_set_message( + t('You do not have any offsite backups. For the highest level of protection, create an offsite destination to save your backups to.', + array( + '!addurl' => url(BACKUP_MIGRATE_MENU_PATH . '/settings/destination/add'), + )), 'warning', FALSE); + } + + return $out; +} + +/** + * List the previously created backups from across multiple destinations. + */ +function backup_migrate_get_saved_backups($refresh = FALSE) { + require_once dirname(__FILE__) . '/includes/destinations.inc'; + require_once dirname(__FILE__) . '/includes/profiles.inc'; + require_once dirname(__FILE__) . '/includes/sources.inc'; + + drupal_add_css(drupal_get_path('module', 'backup_migrate') . '/backup_migrate.css'); + + $remote = FALSE; + $now = $fetch_time = time(); + $out = ''; + $files = $sort = array(); + foreach (backup_migrate_get_destinations('list files') as $destination) { + $dest_id = $destination->get_id(); + $dest_files = $destination->list_files(); + if ($dest_files) { + $location = l($destination->get_name(), BACKUP_MIGRATE_MENU_PATH . '/settings/destination/list/files/' . $dest_id); + if ($destination->get('remote')) { + $location .= $destination->get('remote') ? ' (' . t('offsite') . ')' : ''; + $remote = TRUE; + } + + // Refresh the destination. + if ($refresh) { + $destination->file_cache_clear(); + } + + // Get the file fetch time. + if ($destination->cache_files && $destination->fetch_time) { + $fetch_time = min($destination->fetch_time, $fetch_time); + } + + // Add the files from this destination to the full list. + foreach ($dest_files as $id => $file) { + $dest_files[$id]->file_info['bam_other_safe'][t('Location:')] = $location; + $sort[$dest_id . '/' . $id] = $file->info('filetime'); + $files[$dest_id . '/' . $id] = $file; + } + } + } + array_multisort($sort, SORT_DESC, $files); + + return array($files, $fetch_time, $remote); +} + +/** + * Helper function for byte conversion. + * + * @see README.txt + * @see http://php.net/manual/en/ini.core.php#ini.memory-limit + */ +function backup_migrate_to_bytes($from) { + $passes_sanity_check = preg_match('/^(-1|[0-9]+|[0-9]*(\.[0-9]+)?[mg])$/i', $from); + if (!$passes_sanity_check) { + return 0; + } + + if ($from === '-1') { + return -1; + } + + $number = substr($from, 0, -1); + $unit = strtoupper(substr($from, -1)); + switch ($unit) { + case "M": + return $number * pow(1024, 2); + + break; + + case "G": + return $number * pow(1024, 3); + + break; + + default: + return $number * pow(1024, 2); + } +} + /** * Perform a backup with the given settings. */ function backup_migrate_perform_backup(&$settings) { - backup_migrate_include('destinations', 'files', 'filters'); + require_once dirname(__FILE__) . '/includes/destinations.inc'; + require_once dirname(__FILE__) . '/includes/files.inc'; + require_once dirname(__FILE__) . '/includes/filters.inc'; + timer_start('backup_migrate_backup'); - // If not in 'safe mode', increase the maximum execution time: - if (!ini_get('safe_mode') && strpos(ini_get('disable_functions'), 'set_time_limit') === FALSE && ini_get('max_execution_time') < 1200) { - set_time_limit(variable_get('backup_migrate_backup_max_time', 1200)); + _backup_migrate_set_timeout(); + + // Conditionally increase the memory limit. + // Get the current php memory limit. + $php_memory_limit = ini_get('memory_limit'); + + // If the memory limit is set to -1, there's no need to do anything. + if ($php_memory_limit !== -1) { + // Convert the php memory limit to bytes. + $php_memory_limit_bytes = backup_migrate_to_bytes($php_memory_limit); + + // Get the current Backup Migrate memory limit. + // Hint: To further increase the memory limit used by Backup Migrate add one + // of the following line to the settings.php file: + // * $conf['backup_migrate_memory_limit'] = '512M'; + // * $conf['backup_migrate_memory_limit'] = '1G';. + $backup_migrate_memory_limit = variable_get('backup_migrate_memory_limit', BACKUP_MIGRATE_MEMORY_LIMIT); + + // Convert the Backup Migrate memory limit to bytes. + $backup_migrate_memory_limit_bytes = backup_migrate_to_bytes($backup_migrate_memory_limit); + + // If not in 'safe mode', increase the memory limit. + if (!ini_get('safe_mode') && $php_memory_limit_bytes < $backup_migrate_memory_limit_bytes) { + // Ensure ini_set() is not disabled. + if (!strpos(ini_get('disable_functions'), 'ini_set')) { + // Convert bytes to megabytes, add the 'M' unit. + $backup_migrate_memory_limit = round($backup_migrate_memory_limit_bytes / 1024 / 1024) . 'M'; + + // Update the php memory limit. + ini_set('memory_limit', $backup_migrate_memory_limit); + } + } } - $timestamp = ''; - if ($settings->append_timestamp && $settings->timestamp_format) { - $timestamp = format_date(time(), 'custom', $settings->timestamp_format); + if (variable_get('backup_migrate_verbose')) { + _backup_migrate_message("Memory limit: %limit", array('%limit' => ini_get('memory_limit')), 'success'); } - $filename = _backup_migrate_construct_filename($settings->filename, $timestamp); + // Confirm the destinations are valid. + $valid = backup_migrate_destination_confirm_destination($settings); + if (!$valid) { + backup_migrate_backup_fail("Could not run backup because a there was no valid destination to save to.", array(), $settings); + return FALSE; + } + + // Create the temporary file object to be backed up into. + $filename = _backup_migrate_construct_filename($settings); $file = new backup_file(array('filename' => $filename)); if (!$file) { backup_migrate_backup_fail("Could not run backup because a temporary file could not be created.", array(), $settings); @@ -681,6 +1644,9 @@ function backup_migrate_perform_backup(&$settings) { // Register shutdown callback to deal with timeouts. register_shutdown_function('backup_migrate_shutdown', $settings); + if (variable_get('backup_migrate_verbose')) { + watchdog('backup_migrate', "Starting backup: %file", array('%file' => $file->filename())); + } $file = backup_migrate_filters_backup($file, $settings); if (!$file) { if (_backup_migrate_check_timeout()) { @@ -692,20 +1658,27 @@ function backup_migrate_perform_backup(&$settings) { return FALSE; } + if (variable_get('backup_migrate_verbose')) { + watchdog('backup_migrate', "Preparing to save backup: %file", array('%file' => $file->filename())); + } $file = backup_migrate_destination_save_file($file, $settings); if (!$file) { backup_migrate_backup_fail("Could not run backup because the file could not be saved to the destination.", array(), $settings); return FALSE; } + if (variable_get('backup_migrate_verbose')) { + watchdog('backup_migrate', "Backup succeeded: %file", array('%file' => $file->filename())); + } - // Backup succeeded, + // Backup succeeded. $time = timer_stop('backup_migrate_backup'); - $message = '%source backed up successfully to %file in destination %dest in !time ms. !action'; + $message = '%source backed up successfully to %file (%size) in destination %dest in !time. !action'; $params = array( '%file' => $filename, '%dest' => $settings->get_destination_name(), '%source' => $settings->get_source_name(), - '!time' => $time['time'], + '%size' => format_size($file->filesize()), + '!time' => backup_migrate_format_elapsed_time($time['time']), '!action' => !empty($settings->performed_action) ? $settings->performed_action : '', ); if (($destination = $settings->get_destination()) && ($links = $destination->get_file_links($file->file_id()))) { @@ -716,17 +1689,27 @@ function backup_migrate_perform_backup(&$settings) { return $file; } +/** + * Formats an elapsed time given in milleseconds. + */ +function backup_migrate_format_elapsed_time($time) { + $ms = t('!time ms', array('!time' => $time)); + if ($time < 1000) { + return $ms; + } + return '' . format_interval($time / 1000) . ''; +} + /** * Restore from a file in the given destination. */ function backup_migrate_perform_restore($destination_id, $file, $settings = array()) { - backup_migrate_include('files', 'filters'); + require_once dirname(__FILE__) . '/includes/files.inc'; + require_once dirname(__FILE__) . '/includes/filters.inc'; + timer_start('backup_migrate_restore'); - // If not in 'safe mode', increase the maximum execution time: - if (!ini_get('safe_mode') && strpos(ini_get('disable_functions'), 'set_time_limit') === FALSE && ini_get('max_execution_time') < variable_get('backup_migrate_backup_max_time', 1200)) { - set_time_limit(variable_get('backup_migrate_restore_max_time', 1200)); - } + _backup_migrate_set_timeout(); // Make the settings into a default profile. if (!is_object($settings)) { @@ -746,7 +1729,6 @@ function backup_migrate_perform_restore($destination_id, $file, $settings = arra return FALSE; } } - $file_id = $file->file_id(); // Filter the file and perform the restore. $file = backup_migrate_filters_restore($file, $settings); @@ -764,16 +1746,17 @@ function backup_migrate_perform_restore($destination_id, $file, $settings = arra $time = timer_stop('backup_migrate_restore'); if ($file) { $destination = backup_migrate_get_destination($destination_id); - $message = '%source restored from %dest file %file in !time ms. !action'; + $message = '%source restored from %dest file %file in !time. !action'; $params = array( '%file' => $file->filename(), '%source' => $settings->get_source_name(), '%dest' => $destination->get_name(), - '!time' => $time['time'], + '!time' => backup_migrate_format_elapsed_time($time['time']), '!action' => !empty($settings->performed_action) ? $settings->performed_action : '', ); if ($destination && $destination->op('list files')) { - $params['!links'] = t('Restore again', array('!restoreurl' => url(BACKUP_MIGRATE_MENU_PATH . '/destination/restorefile/'. $destination_id ."/". $file_id))); + $path = $destination->get_settings_path(); + $params['!links'] = t('Restore again', array('!restoreurl' => url($path . '/list/restorefile/' . $destination->get_id() . '/' . $file->file_id()))); } backup_migrate_restore_succeed($message, $params, $settings); } @@ -788,7 +1771,8 @@ function backup_migrate_perform_restore($destination_id, $file, $settings = arra * Clean up when a backup operation fails. */ function backup_migrate_backup_fail($message, $params, $settings) { - backup_migrate_include('files', 'filters'); + require_once dirname(__FILE__) . '/includes/files.inc'; + require_once dirname(__FILE__) . '/includes/filters.inc'; _backup_migrate_message($message, $params, 'error'); backup_migrate_cleanup(); @@ -797,10 +1781,12 @@ function backup_migrate_backup_fail($message, $params, $settings) { } /** - * Clean up when a backup operation suceeds. + * Clean up when a backup operation succeeds. */ function backup_migrate_backup_succeed($message, $params, $settings) { - backup_migrate_include('filters', 'files'); + require_once dirname(__FILE__) . '/includes/files.inc'; + require_once dirname(__FILE__) . '/includes/filters.inc'; + _backup_migrate_message($message, $params, 'success'); backup_migrate_cleanup(); backup_migrate_filters_invoke_all('backup_succeed', $settings, $message, $params); @@ -811,7 +1797,9 @@ function backup_migrate_backup_succeed($message, $params, $settings) { * Clean up when a restore operation fails. */ function backup_migrate_restore_fail($message, $params, $settings) { - backup_migrate_include('files', 'filters'); + require_once dirname(__FILE__) . '/includes/files.inc'; + require_once dirname(__FILE__) . '/includes/filters.inc'; + _backup_migrate_message($message, $params, 'error'); backup_migrate_cleanup(); backup_migrate_filters_invoke_all('restore_fail', $settings, $message, $params); @@ -819,108 +1807,103 @@ function backup_migrate_restore_fail($message, $params, $settings) { } /** - * Clean up when a restore operation suceeds. + * Clean up when a restore operation succeeds. */ function backup_migrate_restore_succeed($message, $params, $settings) { - backup_migrate_include('filters', 'files'); + require_once dirname(__FILE__) . '/includes/files.inc'; + require_once dirname(__FILE__) . '/includes/filters.inc'; + _backup_migrate_message($message, $params, 'success'); backup_migrate_cleanup(); backup_migrate_filters_invoke_all('restore_succeed', $settings, $message, $params); return FALSE; } - /** - * Cleanup after a success or failure. + * Cleans up after a success or failure. */ function backup_migrate_cleanup() { - // Check that the cleanup function exists. If it doesn't then we probably didn't create any files to be cleaned up. + // Check that the cleanup function exists. If it doesn't then we probably + // didn't create any files to be cleaned up. if (function_exists('_backup_migrate_temp_files_delete')) { _backup_migrate_temp_files_delete(); } } - /** - * Shutdown callback. Called when the script terminates even if the script timed out. + * Shutdown callback. + * + * Called when the script terminates even if the script timed out. */ function backup_migrate_shutdown($settings) { - // If we ran out of time, set an error so the user knows what happened + // If we ran out of time, set an error so the user knows what happened. if (_backup_migrate_check_timeout()) { backup_migrate_cleanup(); backup_migrate_backup_fail('The operation timed out. Try increasing your PHP max_execution_time setting.', array('!url' => 'http://www.php.net/manual/en/info.configuration.php#ini.max-execution-time'), $settings); - // The session will have already been written and closed, so we need to write any changes directly. + // The session will have already been written and closed, so we need to + // write any changes directly. _drupal_session_write(session_id(), session_encode()); // Add a redirect or we'll just get whitescreened. drupal_goto(BACKUP_MIGRATE_MENU_PATH); } } - - -/* Actions/Workflow integration */ +/** + * Action to backup the drupal site. + * + * Requires actions.module. + * + * function action_backup_migrate_backup($op, $edit = array()) { + * switch ($op) { + * case 'do': + * _backup_migrate_backup_with_defaults(); + * watchdog('action', 'Backed up database'); + * break;. + * * Case 'metadata': + * return array( + * 'description' => t('Backup the database with the default settings'), + * 'type' => t('Backup and Migrate'), + * 'batchable' => TRUE, + * 'configurable' => FALSE, + * ); + * * // Return an HTML config form for the action. + * * case 'form': + * return ''; + * * // Validate the HTML form. + * * case 'validate': + * return TRUE; + * * // Process the HTML form to store configuration. + * * case 'submit': + * return ''; + * } + * }. + */ /** - * Action to backup the drupal site. Requires actions.module. -function action_backup_migrate_backup($op, $edit = array()) { - switch ($op) { - case 'do': - _backup_migrate_backup_with_defaults(); - watchdog('action', 'Backed up database'); - break; - - case 'metadata': - return array( - 'description' => t('Backup the database with the default settings'), - 'type' => t('Backup and Migrate'), - 'batchable' => TRUE, - 'configurable' => FALSE, - ); - - // Return an HTML config form for the action. - - case 'form': - return ''; - - // Validate the HTML form. - - case 'validate': - return TRUE; - - // Process the HTML form to store configuration. - - case 'submit': - return ''; - } -} + * Implements hook_action_info(). */ +// Function backup_migrate_action_info() { +// return array( +// 'backup_migrate_action_backup' => array( +// 'label' => t('Backup the database'), +// 'description' => t('Backup the database with the default settings.'), +// ), +// ); +// }. -/* - * Implementation of hook_action_info(). -function backup_migrate_action_info() { - return array( - 'backup_migrate_action_backup' => array( - 'label' => t('Backup the database'), - 'description' => t('Backup the database with the default settings.'), - ), - ); -} - */ - -/* +/** * Action callback. */ function backup_migrate_action_backup() { _backup_migrate_backup_with_defaults(); } -/* Utilities */ - /** * Backup the database with the default settings. */ function _backup_migrate_backup_with_defaults($destination_id = "manual") { - backup_migrate_include('files', 'profiles'); + require_once dirname(__FILE__) . '/includes/files.inc'; + require_once dirname(__FILE__) . '/includes/profiles.inc'; $settings = _backup_migrate_profile_saved_default_profile(); $settings->destination_id = $destination_id; @@ -929,7 +1912,9 @@ function _backup_migrate_backup_with_defaults($destination_id = "manual") { } /** - * Helper function to set a drupal message and watchdog message depending on whether the module is being run interactively. + * Helper function to set a Drupal message and watchdog message. + * + * Depends on whether the module is being run interactively. */ function _backup_migrate_message($message, $replace = array(), $type = 'status') { // Only set a message if there is a callback handler to handle the message. @@ -937,23 +1922,32 @@ function _backup_migrate_message($message, $replace = array(), $type = 'status') $callback($message, $replace, $type); } - // Store the message in case it's needed (for the status notification filter for example). + // Store the message in case it's needed (for the status notification filter + // for example). _backup_migrate_messages($message, $replace, $type); } /** - * Helper function to set a drupal message and watchdog message depending on whether the module is being run interactively. + * Helper function to set a Drupal message and watchdog message. + * + * Depends on whether the module is being run interactively. */ function _backup_migrate_messages($message = NULL, $replace = array(), $type = 'status') { static $messages = array(); if ($message) { - $messages[] = array('message' => $message, 'replace' => $replace, 'type' => 'status'); + $messages[] = array( + 'message' => $message, + 'replace' => $replace, + 'type' => $type, + ); } return $messages; } /** - * Send a message to the browser. The normal type of message handling for interactive use. + * Sends a message to the browser. + * + * The normal type of message handling for interactive use. */ function _backup_migrate_message_browser($message, $replace, $type) { // Log the message as well for admins. @@ -968,7 +1962,7 @@ function _backup_migrate_message_browser($message, $replace, $type) { } /** - * Log message if we are in a non-interactive mode such as a cron run. + * Logs a message if we are in a non-interactive mode such as a cron run. */ function _backup_migrate_message_log($message, $replace, $type) { // We only want to log the errors or successful completions. @@ -978,7 +1972,7 @@ function _backup_migrate_message_log($message, $replace, $type) { } /** - * Set or retrieve a message handler. + * Sets or retrieves a message handler. */ function _backup_migrate_message_callback($callback = NULL) { static $current_callback = '_backup_migrate_message_log'; @@ -988,48 +1982,77 @@ function _backup_migrate_message_callback($callback = NULL) { return $current_callback; } +/** + * Increase the execution timeout, and also the database connection timeout. + */ +function _backup_migrate_set_timeout() { + // If not in 'safe mode', increase the maximum execution time: + $backup_max_time = variable_get('backup_migrate_backup_max_time', BACKUP_MIGRATE_BACKUP_MAX_TIME); + if (!ini_get('safe_mode') && ini_get('max_execution_time') < $backup_max_time) { + drupal_set_time_limit($backup_max_time); + } + + // Also increase the time mysql keeps the connection open. + if (Database::getConnection() instanceof DatabaseConnection_mysql) { + $mysql_wait_timeout = db_query('SELECT @@session.wait_timeout')->fetchField(); + if ($mysql_wait_timeout < $backup_max_time) { + // This does not work with placeholders, so sanitize by casting to int. + db_query('SET SESSION wait_timeout = ' . (int) $backup_max_time); + } + } +} + +/** + * Determines if time for a page execution has run out. + */ function _backup_migrate_check_timeout() { static $timeout; // Max execution of 0 means unlimited. if (ini_get('max_execution_time') == 0) { - return false; + return FALSE; } + // Figure out when we should stop execution. if (!$timeout) { - $timeout = (!empty($_SERVER['REQUEST_TIME']) ? $_SERVER['REQUEST_TIME'] : time()) + ini_get('max_execution_time') - variable_get('backup_migrate_timeout_buffer', 5); + $backup_max_time = max(ini_get('max_execution_time'), variable_get('backup_migrate_backup_max_time', BACKUP_MIGRATE_BACKUP_MAX_TIME)); + $timeout = (!empty($_SERVER['REQUEST_TIME']) ? $_SERVER['REQUEST_TIME'] : time()) + $backup_max_time - variable_get('backup_migrate_timeout_buffer', 5); } + return (time() > $timeout); } /** - * Convert an associated array to an ini format string. + * Converts an associated array to an ini format string. */ function _backup_migrate_array_to_ini($data, $prefix = '') { - $content = ""; + $content = ''; foreach ($data as $key => $val) { if ($prefix) { - $key = $prefix . '[' . $key .']'; + $key = $prefix . '[' . $key . ']'; } if (is_array($val)) { $content .= _backup_migrate_array_to_ini($val, $key); } else { - $content .= $key . " = \"". $val ."\"\n"; + $content .= $key . ' = "' . $val . "\"\n"; } } return $content; } /** - * Execute a command line command. Returns false if the function failed. + * Execute a command line command. + * + * @return bool + * Returns FALSE if the function failed. */ function backup_migrate_exec($command, $args = array()) { if (!function_exists('exec') || ini_get('safe_mode')) { return FALSE; } - // Escape the arguments + // Escape the arguments. foreach ($args as $key => $arg) { $args[$key] = escapeshellarg($arg); } @@ -1037,8 +2060,100 @@ function backup_migrate_exec($command, $args = array()) { $output = $result = NULL; // Run the command. - exec($command . ' 2>&1', $output, $result); + exec($command, $output, $result); return $result == 0; } +/** + * Format a size, treating 1GB as 1000 MB rather than 1024 MB. + * + * This is a more standard and expected version for storage (as opposed to + * memory). + */ +function backup_migrate_format_size($size, $langcode = LANGUAGE_NONE) { + $precision = 2; + $multiply = pow(10, $precision); + + if ($size == 0) { + return t('0 bytes', array(), array('langcode' => $langcode)); + } + if ($size < 1024) { + return format_plural($size, '1 byte', '@count bytes', array(), array('langcode' => $langcode)); + } + else { + $size = ceil($size * $multiply / 1024); + $string = '@size KB'; + if ($size >= (1024 * $multiply)) { + $size = ceil($size / 1024); + $string = '@size MB'; + } + if ($size >= 1000 * $multiply) { + $size = ceil($size / 1000); + $string = '@size GB'; + } + if ($size >= 1000 * $multiply) { + $size = ceil($size / 1000); + $string = '@size TB'; + } + return t($string, array('@size' => round($size / $multiply, $precision)), array('langcode' => $langcode)); + } +} + +/** + * Implements hook_mail(). + */ +function backup_migrate_mail($key, &$message, $params) { + if ($key == 'backup_succeed') { + $message['id'] = 'backup_migrate_backup_succeed'; + $message['subject'] = t('!site backup succeeded', array('!site' => variable_get('site_name', 'Drupal site'))); + $message['body'][] = $params['body']; + } + elseif ($key == 'backup_fail') { + $message['id'] = 'backup_migrate_backup_fail'; + $message['subject'] = t('!site backup failed', array('!site' => variable_get('site_name', 'Drupal site'))); + $message['body'][] = $params['body']; + } + elseif ($key == 'destination_mail') { + // @todo translate subject and parts of the body? + $message['subject'] = $params['subject']; + $message['body'][] = $params['body']; + $message['headers'] = array_merge($message['headers'], $params['headers']); + } +} + +/** + * Identify the current memory usage. + * + * Uses the same formula as the Devel module. + * + * @return int + * The current memory usage, in megabytes. + */ +function backup_migrate_get_peak_memory_usage() { + return round(memory_get_peak_usage(TRUE) / 1024 / 1024, 2); +} + +/** + * Returns the "name" element from an array. + * + * Callback for array_map() within + * backup_migrate_destination_db_mysql::_restore_db_from_file() and + * backup_migrate_source_db_mysql::_restore_db_from_file(). + */ +function backup_migrate_array_name_value($a) { + return $a["name"]; +} + +/** + * Checks if a php.ini 'memory_limit' style value is valid. + * + * See http://php.net/manual/en/ini.core.php#ini.memory-limit for what are + * valid values. + */ +function _backup_migrate_is_valid_memory_limit($value) { + $test = preg_match('/^(-1|[0-9]+|[0-9]*(\.[0-9]+)?[mg])$/i', $value); + // preg_match() returns truthy or falsy values, this makes it explicit. + $out = $test ? TRUE : FALSE; + return $test; +} diff --git a/sites/all/modules/contrib/admin/backup_migrate/includes/backup_migrate.drush.inc b/sites/all/modules/contrib/admin/backup_migrate/includes/backup_migrate.drush.inc index 73097cb..13d57a3 100644 --- a/sites/all/modules/contrib/admin/backup_migrate/includes/backup_migrate.drush.inc +++ b/sites/all/modules/contrib/admin/backup_migrate/includes/backup_migrate.drush.inc @@ -1,13 +1,12 @@ dt('Backup the site\'s database with Backup and Migrate.'), 'aliases' => array('bb'), 'examples' => array( - 'drush bam-backup' => 'Backup the default databse to the manual backup directory using the default settings.', - 'drush bam-backup db scheduled mysettings' => 'Backup the database to the scheduled directory using a settings profile called "mysettings"', - 'drush bam-backup files' => 'Backup the files directory to the manual directory using the default settings. The Backup and Migrate Files module is required for files backups.', + 'drush bam-backup' => 'Backup the default database to the manual backup directory using the default settings.', + 'drush bam-backup db scheduled mysettings' => 'Backup the database to the scheduled directory using a settings profile called "mysettings"', + 'drush bam-backup files' => 'Backup the files directory to the manual directory using the default settings.', ), 'arguments' => array( 'source' => "Optional. The id of the source (usually a database) to backup. Use 'drush bam-sources' to get a list of sources. Defaults to 'db'", @@ -41,7 +40,6 @@ function backup_migrate_drush_command() { 'callback' => 'backup_migrate_drush_destinations', 'description' => dt('Get a list of available destinations.'), ); - $items['bam-sources'] = array( 'callback' => 'backup_migrate_drush_sources', 'description' => dt('Get a list of available sources.'), @@ -54,25 +52,40 @@ function backup_migrate_drush_command() { 'callback' => 'backup_migrate_drush_destination_files', 'description' => dt('Get a list of previously created backup files.'), 'arguments' => array( - 'destination' => "Required. The id of destination to list backups from. Use 'drush bam-destinations' to get a list of destinations.", + 'destination' => "Optional. The id of destination to list backups from. Use 'drush bam-destinations' to get a list of destinations.", ), ); + $items['bam-schedule'] = array( + 'callback' => 'backup_migrate_drush_schedule', + 'description' => dt('Backup using a specific schedule.'), + 'arguments' => array( + 'schedule_id' => dt('The ID of the schedule to run.'), + ), + ); + $items['bam-schedules'] = array( + 'callback' => 'backup_migrate_drush_schedules', + 'description' => dt('Get a list of available schedules.'), + ); return $items; } /** - * Implementation of hook_drush_help(). + * Implements hook_drush_help(). */ function backup_migrate_drush_help($section) { switch ($section) { case 'drush:bam-backup': return dt("Backup the site's database using default settings."); + case 'drush:bam-restore': return dt('Restore the site\'s database with Backup and Migrate.'); + case 'drush:bam-destinations': return dt('Get a list of available destinations.'); + case 'drush:bam-profiles': return dt('Get a list of available settings profiles.'); + case 'drush:bam-backups': return dt('Get a list of previously created backup files.'); } @@ -82,12 +95,14 @@ function backup_migrate_drush_help($section) { * Backup the default database. */ function backup_migrate_drush_backup($source_id = 'db', $destination_id = 'manual', $profile_id = 'default') { - backup_migrate_include('profiles', 'destinations'); + require_once dirname(__FILE__) . '/destinations.inc'; + require_once dirname(__FILE__) . '/profiles.inc'; + require_once dirname(__FILE__) . '/sources.inc'; // Set the message mode to logging. _backup_migrate_message_callback('_backup_migrate_message_drush'); - if (!backup_migrate_get_destination($source_id)) { + if (!backup_migrate_get_source($source_id)) { _backup_migrate_message("Could not find the source '@source'. Try using 'drush bam-sources' to get a list of available sources or use 'db' to backup the Drupal database.", array('@source' => $source_id), 'error'); return; } @@ -96,7 +111,7 @@ function backup_migrate_drush_backup($source_id = 'db', $destination_id = 'manua return; } $settings = backup_migrate_get_profile($profile_id); - if(!$settings) { + if (!$settings) { _backup_migrate_message("Could not find the profile '@profile'. Try using 'drush bam-profiles' to get a list of available profiles.", array('@profile' => $profile_id), 'error'); return; } @@ -108,20 +123,57 @@ function backup_migrate_drush_backup($source_id = 'db', $destination_id = 'manua } /** - * Restore to the default database. + * Backup using schedule. */ -function backup_migrate_drush_restore($source_id = '', $destination_id = '', $file_id = '') { - drush_print(dt('Restoring will delete some or all of your data and cannot be undone. ALWAYS TEST YOUR BACKUPS ON A NON-PRODUCTION SERVER!')); - if (!drush_confirm(dt('Are you sure you want to restore the database?'))) { - return drush_user_abort(); - } - - backup_migrate_include('profiles', 'destinations'); +function backup_migrate_drush_schedule($schedule_id = '') { + require_once dirname(__FILE__) . '/schedules.inc'; // Set the message mode to drush output. _backup_migrate_message_callback('_backup_migrate_message_drush'); - if (!backup_migrate_get_destination($source_id)) { + if (!($schedule = backup_migrate_get_schedule($schedule_id))) { + _backup_migrate_message("Could not find the schedule '@schedule'. Try using 'drush bam-schedules' to get a list of available schedules.", array('@schedule' => $schedule_id), 'error'); + return; + } + + if (!$schedule->enabled) { + _backup_migrate_message("Nothing to do, the schedule '@schedule' is disabled.", array('@schedule' => $schedule_id), 'warning'); + return; + } + + _backup_migrate_message("Starting schedule '$schedule_id'..."); + + backup_migrate_schedule_run($schedule_id); +} + +/** + * Get a list of available destinations. + */ +function backup_migrate_drush_schedules() { + require_once dirname(__FILE__) . '/schedules.inc'; + + $rows = array(array(dt('ID'), dt('Name'))); + foreach (backup_migrate_get_schedules() as $schedule) { + $rows[] = array( + $schedule->get_id(), + $schedule->get_name(), + ); + } + drush_print_table($rows, TRUE, array(32, 32)); +} + +/** + * Restore to the default database. + */ +function backup_migrate_drush_restore($source_id = '', $destination_id = '', $file_id = '') { + require_once dirname(__FILE__) . '/destinations.inc'; + require_once dirname(__FILE__) . '/profiles.inc'; + require_once dirname(__FILE__) . '/sources.inc'; + + // Set the message mode to drush output. + _backup_migrate_message_callback('_backup_migrate_message_drush'); + + if (!backup_migrate_get_source($source_id)) { _backup_migrate_message("Could not find the source '@source'. Try using 'drush bam-sources' to get a list of available sources or use 'db' to backup the Drupal database.", array('@source' => $source_id), 'error'); return; } @@ -129,14 +181,18 @@ function backup_migrate_drush_restore($source_id = '', $destination_id = '', $fi _backup_migrate_message("Could not find the destination '@destination'. Try using 'drush bam-destinations' to get a list of available destinations.", array('@destination' => $destination_id), 'error'); return; } - if (!$file_id || !$file = backup_migrate_destination_get_file($destination_id, $file_id)) { + elseif (!$file_id || !$file = backup_migrate_destination_get_file($destination_id, $file_id)) { _backup_migrate_message("Could not find the file '@file'. Try using 'drush bam-backups @destination' to get a list of available backup files in this destination destinations.", array('@destination' => $destination_id, '@file' => $file_id), 'error'); return; } + drush_print(dt('Restoring will delete some or all of your data and cannot be undone. ALWAYS TEST YOUR BACKUPS ON A NON-PRODUCTION SERVER!')); + if (!drush_confirm(dt('Are you sure you want to perform the restore?'))) { + return drush_user_abort(); + } _backup_migrate_message('Starting restore...'); $settings = array('source_id' => $source_id); - backup_migrate_perform_restore($destination_id, $file_id); + backup_migrate_perform_restore($destination_id, $file_id, $settings); } /** @@ -150,21 +206,38 @@ function backup_migrate_drush_destinations() { * Get a list of available sources. */ function backup_migrate_drush_sources() { - return _backup_migrate_drush_destinations('source'); + return _backup_migrate_drush_sources('source'); } - /** * Get a list of available destinations with the given op. */ function _backup_migrate_drush_destinations($op = NULL) { - backup_migrate_include('destinations'); + require_once dirname(__FILE__) . '/destinations.inc'; + $rows = array(array(dt('ID'), dt('Name'), dt('Operations'))); foreach (backup_migrate_get_destinations($op) as $destination) { $rows[] = array( $destination->get_id(), $destination->get_name(), - implode (', ', $destination->ops()), + implode(', ', $destination->ops()), + ); + } + drush_print_table($rows, TRUE, array(32, 32)); +} + +/** + * Get a list of available destinations with the given op. + */ +function _backup_migrate_drush_sources($op = NULL) { + require_once dirname(__FILE__) . '/sources.inc'; + + $rows = array(array(dt('ID'), dt('Name'), dt('Operations'))); + foreach (backup_migrate_get_sources($op) as $destination) { + $rows[] = array( + $destination->get_id(), + $destination->get_name(), + implode(', ', $destination->ops()), ); } drush_print_table($rows, TRUE, array(32, 32)); @@ -174,7 +247,8 @@ function _backup_migrate_drush_destinations($op = NULL) { * Get a list of available profiles. */ function backup_migrate_drush_profiles() { - backup_migrate_include('profiles'); + require_once dirname(__FILE__) . '/profiles.inc'; + $rows = array(array(dt('ID'), dt('Name'))); foreach (backup_migrate_get_profiles() as $profile) { $rows[] = array( @@ -186,46 +260,60 @@ function backup_migrate_drush_profiles() { } /** - * Get a list of files in a given destination + * Get a list of files in a given destination. */ function backup_migrate_drush_destination_files($destination_id = NULL) { - backup_migrate_include('destinations'); + require_once dirname(__FILE__) . '/destinations.inc'; + + $destinations = array(); // Set the message mode to drush output. _backup_migrate_message_callback('_backup_migrate_message_drush'); - - if (!$destination_id) { - _backup_migrate_message("You must specify an existing destination. Try using 'drush bam-destinations' to get a list of available destinations.", array('@destination' => $destination_id), 'error'); - return; - } - if (!$destination = backup_migrate_get_destination($destination_id)) { + if ($destination_id && !$destination = backup_migrate_get_destination($destination_id)) { _backup_migrate_message("Could not find the destination '@destination'. Try using 'drush bam-destinations' to get a list of available destinations.", array('@destination' => $destination_id), 'error'); return; } - $out = array(array( - dt('Filename'), - dt('Date'), - dt('Age'), - dt('Size'), - )); + // Single destination required. + if ($destination) { + $destinations = array($destination); + } + // List all destinations. + else { + $destinations = backup_migrate_get_destinations('list files'); + } - $files = $destination->list_files(); - $i = 0; - foreach ((array)$files as $file) { - // Show only files that can be restored from. - if ($file->is_recognized_type()) { + // Load all the files. + $rows = $sort = array(); + foreach ($destinations as $destination) { + $destination->file_cache_clear(); + $dest_files = $destination->list_files(); + foreach ($dest_files as $id => $file) { $info = $file->info(); - $out[] = array( + $rows[] = array( check_plain($info['filename']), + $destination->get_id(), format_date($info['filetime'], 'small'), format_interval(time() - $info['filetime'], 1), format_size($info['filesize']), ); + $sort[] = $info['filetime']; } } - if (count($out) > 1) { - drush_print_table($out, TRUE); + + $headers = array( + array( + dt('Filename'), + dt('Destination'), + dt('Date'), + dt('Age'), + dt('Size'), + ), + ); + + if (count($rows)) { + array_multisort($sort, SORT_DESC, $rows); + drush_print_table(array_merge($headers, $rows), TRUE); } else { drush_print(dt('There are no backup files to display.')); @@ -236,8 +324,15 @@ function backup_migrate_drush_destination_files($destination_id = NULL) { * Send a message to the drush log. */ function _backup_migrate_message_drush($message, $replace, $type) { - // Use drush_log to display to the user. - drush_log(strip_tags(dt($message, $replace)), str_replace('status', 'notice', $type)); + // If this is an error use drush_set_error to notify the end user and set the + // exit status. + if ($type == 'error') { + drush_set_error(strip_tags(dt($message, $replace))); + } + else { + // Use drush_log to display to the user. + drush_log(strip_tags(dt($message, $replace)), str_replace('status', 'notice', $type)); + } // Watchdog log the message as well for admins. _backup_migrate_message_log($message, $replace, $type); } diff --git a/sites/all/modules/contrib/admin/backup_migrate/includes/crud.inc b/sites/all/modules/contrib/admin/backup_migrate/includes/crud.inc index 6461b68..6cc3f26 100644 --- a/sites/all/modules/contrib/admin/backup_migrate/includes/crud.inc +++ b/sites/all/modules/contrib/admin/backup_migrate/includes/crud.inc @@ -14,6 +14,14 @@ define('BACKUP_MIGRATE_STORAGE_OVERRIDEN', 2); */ function backup_migrate_crud_types() { $out = array( + 'schedule' => array( + 'class' => 'backup_migrate_schedule', + 'include' => 'schedules', + ), + 'source' => array( + 'class' => 'backup_migrate_source', + 'include' => 'sources', + ), 'destination' => array( 'class' => 'backup_migrate_destination', 'include' => 'destinations', @@ -22,32 +30,102 @@ function backup_migrate_crud_types() { 'class' => 'backup_migrate_profile', 'include' => 'profiles', ), - 'schedule' => array( - 'class' => 'backup_migrate_schedule', - 'include' => 'schedules', - ), ); return $out; } +/** + * Get the info for a particular crud type. + */ +function backup_migrate_crud_type_info($type) { + $types = backup_migrate_crud_types(); + if (isset($types[$type])) { + return $types[$type]; + } + return NULL; +} + +/** + * Get a list of avaiable classes of each crud type. + */ +function backup_migrate_crud_subtypes($type) { + $out = array(); + if ($info = backup_migrate_crud_type_info($type)) { + // Include the function that contains the type base. + if (!empty($info['include'])) { + require_once dirname(__FILE__) . '/' . $info['include'] . '.inc'; + } + + // Allow modules (including this one) to declare backup and migrate + // subtypes. We don't use module_invoke_all so we can avoid the + // side-effects of array_merge_recursive. + $out = array(); + foreach (module_implements('backup_migrate_' . $type . '_subtypes') as $module) { + $function = $module . '_backup_migrate_' . $type . '_subtypes'; + $result = $function(); + if (isset($result) && is_array($result)) { + foreach ($result as $key => $val) { + $out[$key] = $val; + } + } + } + } + + return $out; +} + +/** + * Get the info for a particular crud subtype. + */ +function backup_migrate_crud_subtype_info($type, $subtype) { + $types = backup_migrate_crud_subtypes($type); + if (isset($types[$subtype])) { + return $types[$subtype]; + } + return NULL; +} + /** * Get a generic object of the given type to be used for static-like functions. * - * I'm not using actual static method calls since they don't work on variables prior to PHP 5.3.0 + * I'm not using actual static method calls since they don't work on variables + * prior to PHP 5.3.0. */ -function backup_migrate_crud_type_load($type) { - $out = NULL; - $types = backup_migrate_crud_types(); - if (!empty($types[$type])) { - $info = $types[$type]; - if ($info['include']) { - backup_migrate_include($info['include']); +function backup_migrate_crud_type_load($type, $subtype = NULL) { + $out = $info = NULL; + + if ($subtype) { + $info = backup_migrate_crud_subtype_info($type, $subtype); + } + else { + $info = backup_migrate_crud_type_info($type); + } + + if (!empty($info)) { + if (!empty($info['include'])) { + require_once dirname(__FILE__) . '/' . $info['include'] . '.inc'; + } + if (!empty($info['file'])) { + require_once './' . (isset($info['path']) ? $info['path'] : '') . $info['file']; + } + + if (class_exists($info['class'])) { + $out = new $info['class'](); + $out = $out->create(array('subtype' => $subtype)); } - $out = new $info['class']; } return $out; } +/** + * Page callback to create a new item. + */ +function backup_migrate_crud_create($type, $subtype = NULL) { + if ($item = backup_migrate_crud_type_load($type, $subtype)) { + return $item; + } + return NULL; +} /** * Get the menu items handled by the CRUD code. @@ -55,8 +133,12 @@ function backup_migrate_crud_type_load($type) { function backup_migrate_crud_menu() { $items = array(); foreach (backup_migrate_crud_types() as $type => $info) { - $type = backup_migrate_crud_type_load($type); - $items += (array)$type->get_menu_items(); + $item = backup_migrate_crud_type_load($type); + $items += (array) $item->get_menu_items(); + foreach (backup_migrate_crud_subtypes($type) as $subtype => $info) { + $subitem = backup_migrate_crud_type_load($type, $subtype); + $items += (array) $subitem->get_menu_items(); + } } return $items; } @@ -64,47 +146,69 @@ function backup_migrate_crud_menu() { /** * Page callback to create a new item. */ -function backup_migrate_crud_ui_create() { - if ($type = backup_migrate_crud_type_load(arg(BACKUP_MIGRATE_MENU_DEPTH))) { - $item = $type->create(array()); +function backup_migrate_crud_ui_create($type, $subtype = NULL) { + if ($item = backup_migrate_crud_create($type, $subtype)) { return drupal_get_form('backup_migrate_crud_edit_form', $item); } + return drupal_not_found(); } /** * Page callback to list all items. */ -function backup_migrate_crud_ui_list() { +function backup_migrate_crud_ui_list($type) { $out = ''; - if ($type = backup_migrate_crud_type_load(arg(BACKUP_MIGRATE_MENU_DEPTH))) { + if ($type = backup_migrate_crud_type_load($type)) { $out = $type->get_list(); } return $out; } +/** + * Page callback to list all items. + */ +function backup_migrate_crud_ui_list_all() { + $out = array(); + foreach (backup_migrate_crud_types() as $type => $info) { + $type = backup_migrate_crud_type_load($type); + $out[] = theme('backup_migrate_group', array('title' => t($type->title_plural), 'body' => $type->get_list())); + } + return implode('', $out); +} + /** * Page callback to edit an item. */ -function backup_migrate_crud_ui_edit($item_id = NULL) { - if ($type = backup_migrate_crud_type_load(arg(BACKUP_MIGRATE_MENU_DEPTH))) { +function backup_migrate_crud_ui_edit($type, $item_id = NULL) { + if ($type = backup_migrate_crud_type_load($type)) { if ($item_id && $item = $type->item($item_id)) { return drupal_get_form('backup_migrate_crud_edit_form', $item); } - drupal_goto(BACKUP_MIGRATE_MENU_PATH. '/' . arg(BACKUP_MIGRATE_MENU_DEPTH)); + drupal_goto($type->get_settings_path()); } } +/** + * Does a crud item with the given name exist. + * + * Callback for the 'machine_name' form type. + */ +function backup_migrate_crud_item_exists($machine_name, $element, $form_state) { + return $form_state['values']['item']->item_exists($machine_name); +} + /** * A form callback to edit an item. */ function backup_migrate_crud_edit_form($form, $form_state, $item) { $form = $item->edit_form(); - $form['item'] = array( + + $form['item'] = array( '#type' => 'value', '#value' => $item, ); - $form['#validate'][] = 'backup_migrate_crud_edit_form_validate'; - $form['#submit'][] = 'backup_migrate_crud_edit_form_submit'; + $form['#validate'][] = 'backup_migrate_crud_edit_form_validate'; + $form['#submit'][] = 'backup_migrate_crud_edit_form_submit'; return $form; } @@ -122,22 +226,22 @@ function backup_migrate_crud_edit_form_validate($form, &$form_state) { */ function backup_migrate_crud_edit_form_submit($form, &$form_state) { $item = $form_state['values']['item']; + $item->edit_form_submit($form, $form_state); if (empty($form_state['redirect'])) { - $form_state['redirect'] = BACKUP_MIGRATE_MENU_PATH . '/'. $item->type_name; + $form_state['redirect'] = $item->get_settings_path(); } } - /** * Page callback to delete an item. */ -function backup_migrate_crud_ui_delete($item_id = NULL) { - if ($type = backup_migrate_crud_type_load(arg(BACKUP_MIGRATE_MENU_DEPTH))) { +function backup_migrate_crud_ui_delete($type, $item_id = NULL) { + if ($type = backup_migrate_crud_type_load($type)) { if ($item_id && $item = $type->item($item_id)) { return drupal_get_form('backup_migrate_crud_delete_confirm_form', $item); } - drupal_goto('admin/content/backup_migrate/'. arg(BACKUP_MIGRATE_MENU_DEPTH)); + drupal_goto($type->get_settings_path()); } } @@ -149,8 +253,14 @@ function backup_migrate_crud_delete_confirm_form($form, &$form_state, $item) { '#type' => 'value', '#value' => $item, ); - $message = $item->delete_confirm_message(); - return confirm_form($form, t('Are you sure?'), BACKUP_MIGRATE_MENU_PATH . '/'. $item->type_name, $message, t('Delete'), t('Cancel')); + if ($item->storage == BACKUP_MIGRATE_STORAGE_OVERRIDEN) { + $message = $item->revert_confirm_message(); + return confirm_form($form, t('Are you sure?'), $item->get_settings_path(), $message, t('Revert'), t('Cancel')); + } + else { + $message = $item->delete_confirm_message(); + return confirm_form($form, t('Are you sure?'), $item->get_settings_path(), $message, t('Delete'), t('Cancel')); + } } /** @@ -161,18 +271,18 @@ function backup_migrate_crud_delete_confirm_form_submit($form, &$form_state) { $item = $form_state['values']['item']; $item->delete(); } - $form_state['redirect'] = BACKUP_MIGRATE_MENU_PATH . "/". $item->type_name; + $form_state['redirect'] = $item->get_settings_path(); } /** * Export an item. */ -function backup_migrate_crud_ui_export($item_id = NULL) { - if ($type = backup_migrate_crud_type_load(arg(BACKUP_MIGRATE_MENU_DEPTH))) { +function backup_migrate_crud_ui_export($type, $item_id = NULL) { + if ($type = backup_migrate_crud_type_load($type)) { if ($item_id && $item = $type->item($item_id)) { return drupal_get_form('backup_migrate_crud_export_form', $item->export()); } - drupal_goto(BACKUP_MIGRATE_MENU_PATH . '/' . arg(BACKUP_MIGRATE_MENU_DEPTH)); + drupal_goto($type->get_settings_path()); } } @@ -189,6 +299,72 @@ function backup_migrate_crud_export_form($form, &$form_state, $export) { return $form; } +/** + * Page callback to import an item. + */ +function backup_migrate_crud_ui_import() { + return drupal_get_form('backup_migrate_crud_import_form'); +} + +/** + * Ask confirmation for deletion of a item. + */ +function backup_migrate_crud_import_form($form, &$form_state) { + $form['code'] = array( + '#type' => 'textarea', + '#title' => t('Paste Exported Code Here'), + '#required' => TRUE, + ); + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Import'), + ); + return $form; +} + +/** + * Validate handler to import a view. + */ +function backup_migrate_crud_import_form_validate($form, &$form_state) { + $item = backup_migrate_crud_create_from_import($form_state['values']['code']); + if ($item) { + $form_state['values']['item'] = $item; + } + else { + form_set_error('code', t('Unable to import item.')); + } +} + +/** + * Import a item after confirmation. + */ +function backup_migrate_crud_import_form_submit($form, &$form_state) { + $item = $form_state['values']['item']; + $item->save(); + _backup_migrate_message('Your !type was imported', array('!type' => t($item->singular))); + $form_state['redirect'] = $item->get_settings_path(); +} + +/** + * Create an object from the exported object. + */ +function backup_migrate_crud_create_from_import($code) { + $item = NULL; + $code = 'return ' . $code . ';'; + ob_start(); + $values = eval($code); + ob_end_clean(); + + if ($values) { + if (!empty($values['type_name']) && $type = backup_migrate_crud_type_load($values['type_name'])) { + $item = $type->create($values); + // Make sure the item's ID doesn't already exist. + $item->unique_id(); + } + } + return $item; +} + /** * Get all items of the given type. */ @@ -198,7 +374,6 @@ function backup_migrate_crud_get_items($type) { } } - /** * Get an item of the specified type. */ @@ -218,57 +393,98 @@ function backup_migrate_crud_create_item($type, $params) { } /** - * A base class for items which can be stored in the database, listed, edited, deleted etc. + * A base class for items which can be stored in the database. */ class backup_migrate_item { - var $db_table = ''; - var $type_name = ''; - var $storage = FALSE; - var $default_values = array(); - var $singular = 'item'; - var $plural = 'items'; + public $show_in_list = TRUE; + public $settings_path = '/settings/'; + public $db_table = ''; + public $type_name = ''; + public $storage = FALSE; + public $default_values = array(); + public $singular = 'item'; + public $plural = 'items'; + public $title_plural = 'Items'; + public $title_singular = 'Item'; /** - * Constructor, set the basic info pulled from the db or generated programatically. + * This function is not supposed to be called. + * + * It is just here to help the po extractor out. */ - function __construct($params = array()) { - $this->from_array((array)$params + (array)$this->get_default_values()); + public function strings() { + // Help the pot extractor find these strings. + t('item'); + t('items'); + t('Items'); + t('Item'); + + // Help the pot extractor find these strings. + t('List !type'); + t('Create !type'); + t('Delete !type'); + t('Edit !type'); + t('Export !type'); + } + + /** + * Set the basic info pulled from the db or generated programatically. + */ + public function __construct($params = array()) { + $this->from_array($this->_merge_defaults((array) $params, (array) $this->get_default_values())); + } + + /** + * Merge parameters with the given defaults. + * + * Works like array_merge_recursive, but it doesn't turn scalar values into + * arrays. + */ + public function _merge_defaults($params, $defaults) { + foreach ($defaults as $key => $val) { + if (!isset($params[$key])) { + $params[$key] = $val; + } + elseif (is_array($params[$key])) { + $params[$key] = $this->_merge_defaults($params[$key], $val); + } + } + return $params; } /** * Get the default values for standard parameters. */ - function get_default_values() { + public function get_default_values() { return $this->default_values; } /** * Save the item to the database. - */ - function save() { + */ + public function save() { if (!$this->get_id()) { - $this->generate_id(); + $this->unique_id(); } - $data = $this->to_array(); - drupal_write_record($this->db_table, $data, !empty($this->storage) ? $this->get_primary_key() : array()); + $record = $this->to_array(); + drupal_write_record($this->db_table, $record, !empty($this->storage) ? $this->get_primary_key() : array()); } /** * Delete the item from the database. - */ - function delete() { - $keys = (array)$this->get_primary_key(); + */ + public function delete() { + $keys = (array) $this->get_machine_name_field(); db_query('DELETE FROM {' . $this->db_table . '} WHERE ' . $keys[0] . ' = :id', array(':id' => $this->get_id())); } - /** * Load an existing item from an array. */ - function from_array($params) { + public function from_array($params) { foreach ($params as $key => $value) { - if (method_exists($this, 'set_'. $key)) { - $this->{'set_'. $key}($value); + if (method_exists($this, 'set_' . $key)) { + $this->{'set_' . $key}($value); } else { $this->{$key} = $value; @@ -279,7 +495,7 @@ class backup_migrate_item { /** * Return as an array of values. */ - function to_array() { + public function to_array() { $out = array(); // Return fields as specified in the schema. $schema = $this->get_schema(); @@ -294,8 +510,10 @@ class backup_migrate_item { /** * Return as an exported array of values. */ - function export() { + public function export() { $out = $this->to_array(); + $out['type_name'] = $this->type_name; + ob_start(); var_export($out); $out = ob_get_contents(); @@ -306,7 +524,7 @@ class backup_migrate_item { /** * Load an existing item from an database (serialized) array. */ - function load_row($data) { + public function load_row($data) { $params = array(); $schema = $this->get_schema(); // Load fields as specified in the schema. @@ -316,11 +534,10 @@ class backup_migrate_item { $this->from_array($params); } - /** * Decode a loaded db row (unserialize necessary fields). */ - function decode_db_row($data) { + public function decode_db_row($data) { $params = array(); $schema = $this->get_schema(); // Load fields as specified in the schema. @@ -333,7 +550,7 @@ class backup_migrate_item { /** * Return the fields which must be serialized before saving to the db. */ - function get_serialized_fields() { + public function get_serialized_fields() { $out = array(); $schema = $this->get_schema(); foreach ($schema['fields'] as $field => $info) { @@ -347,15 +564,26 @@ class backup_migrate_item { /** * Get the primary key field title from the schema. */ - function get_primary_key() { + public function get_primary_key() { $schema = $this->get_schema(); return @$schema['primary key']; } + /** + * Get the machine name field name from the schema. + */ + public function get_machine_name_field() { + $schema = $this->get_schema(); + if (isset($schema['export']['key'])) { + return $schema['export']['key']; + } + return @$schema['primary key']; + } + /** * Get the schema for the item type. */ - function get_schema() { + public function get_schema() { return drupal_get_schema($this->db_table); } @@ -364,16 +592,16 @@ class backup_migrate_item { * * We only handle single field keys since that's all we need. */ - function get_id() { - $keys = (array)$this->get_primary_key(); - return !empty($this->{$keys[0]}) ? (string)$this->{$keys[0]} : ''; + public function get_id() { + $keys = (array) $this->get_machine_name_field(); + return !empty($keys[0]) && !empty($this->{$keys[0]}) ? (string) $this->{$keys[0]} : ''; } /** * Set the primary id for this item (if any is set). */ - function set_id($id) { - $keys = (array)$this->get_primary_key(); + public function set_id($id) { + $keys = (array) $this->get_machine_name_field(); if (!empty($keys[0])) { return $this->{$keys[0]} = $id; } @@ -383,65 +611,97 @@ class backup_migrate_item { /** * Return a random (very very likely unique) string id for a new item. */ - function generate_id() { - $this->set_id(md5(uniqid(mt_rand(), true))); + public function generate_id() { + $id = md5(uniqid(mt_rand(), TRUE)); + + // Find the shortest possible unique id from (min 4 chars). + for ($i = 4; $i < 32; $i++) { + $new_id = substr($id, 0, $i); + if (!$this->item($new_id)) { + return $new_id; + } + } + // If we get here, then all 28 increasingly complex ids were already taken + // so we'll try again; this could theoretially lead to an infinite loop, + // but the odds are incredibly low. + return $this->generate_id(); + } + + /** + * Make sure this item has a unique id. + * + * Should only be called for new items or the item will collide with itself. + */ + public function unique_id() { + $id = $this->get_id(); + + // Unset the autoincrement field so it can be regenerated. + foreach ((array) $this->get_primary_key() as $key) { + $this->{$key} = NULL; + } + + // If the item doesn't have an ID or if it's id is already taken, generate + // random one. + if (!$id || $this->item($id)) { + $this->set_id($this->generate_id()); + } } /** * Get the name of the item. */ - function get_name() { + public function get_name() { return @$this->name; } /** * Get the member with the given key. - */ - function get($key) { - if (method_exists($this, 'get_'. $key)) { - return $this->{'get_'. $key}(); + */ + public function get($key) { + if (method_exists($this, 'get_' . $key)) { + return $this->{'get_' . $key}(); } return @$this->{$key}; } - /* UI Stuff */ - /** * Get the action links for a destination. */ - function get_action_links() { - $out = array('edit' => '', 'delete' => ''); + public function get_action_links() { + $out = array(); $item_id = $this->get_id(); + $path = $this->get_settings_path(); + if (@$this->storage == BACKUP_MIGRATE_STORAGE_DB || @$this->storage == BACKUP_MIGRATE_STORAGE_OVERRIDEN) { - $out['edit'] = l(t("edit"), BACKUP_MIGRATE_MENU_PATH . "/$this->type_name/list/edit/$item_id"); + $out['edit'] = l(t("edit"), $path . "/edit/$item_id"); } - else if (@$this->storage == BACKUP_MIGRATE_STORAGE_NONE) { - $out['edit'] = l(t("override"), BACKUP_MIGRATE_MENU_PATH . "/$this->type_name/list/edit/$item_id"); + elseif (@$this->storage == BACKUP_MIGRATE_STORAGE_NONE) { + $out['edit'] = l(t("override"), $path . "/edit/$item_id"); } if (@$this->storage == BACKUP_MIGRATE_STORAGE_DB) { - $out['delete'] = l(t("delete"), BACKUP_MIGRATE_MENU_PATH . "/$this->type_name/list/delete/$item_id"); + $out['delete'] = l(t("delete"), $path . "/delete/$item_id"); } - else if (@$this->storage == BACKUP_MIGRATE_STORAGE_OVERRIDEN) { - $out['delete'] = l(t("revert"), BACKUP_MIGRATE_MENU_PATH . "/$this->type_name/list/delete/$item_id"); + elseif (@$this->storage == BACKUP_MIGRATE_STORAGE_OVERRIDEN) { + $out['delete'] = l(t("revert"), $path . "/delete/$item_id"); } - - // Export link disabled until we have an import function. - //$out['export'] = l(t("export"), BACKUP_MIGRATE_MENU_PATH . "/$this->type_name/list/export/$item_id"); + $out['export'] = l(t("export"), $path . "/export/$item_id"); return $out; } /** * Get a table of all items of this type. - */ - function get_list() { + */ + public function get_list() { $items = $this->all_items(); $rows = array(); - foreach ($items as $item) { - if ($row = $item->get_list_row()) { - $rows[] = $row; + foreach ((array) $items as $item) { + if ($item->show_in_list()) { + if ($row = $item->get_list_row()) { + $rows[] = $row; + } } } if (count($rows)) { @@ -450,13 +710,30 @@ class backup_migrate_item { else { $out = t('There are no !items to display.', array('!items' => $this->plural)); } + if (user_access('administer backup and migrate')) { + $out .= ' ' . l(t('Create a new !item', array('!item' => $this->singular)), $this->get_settings_path() . '/add'); + } return $out; } /** * Get the columns needed to list the type. - */ - function get_list_column_info() { + */ + public function show_in_list() { + return $this->show_in_list; + } + + /** + * Get the columns needed to list the type. + */ + public function get_settings_path() { + return BACKUP_MIGRATE_MENU_PATH . $this->settings_path . $this->type_name; + } + + /** + * Get the columns needed to list the type. + */ + public function get_list_column_info() { return array( 'actions' => array('title' => t('Operations'), 'html' => TRUE), ); @@ -464,8 +741,8 @@ class backup_migrate_item { /** * Get header for a lost of this type. - */ - function get_list_header() { + */ + public function get_list_header() { $out = array(); foreach ($this->get_list_column_info() as $key => $col) { $out[] = $col['title']; @@ -475,11 +752,14 @@ class backup_migrate_item { /** * Get a row of data to be used in a list of items of this type. - */ - function get_list_row() { + */ + public function get_list_row() { $out = array(); foreach ($this->get_list_column_info() as $key => $col) { $out[$key] = empty($col['html']) ? check_plain($this->get($key)) : $this->get($key); + if (isset($col['class'])) { + $out[$key] = array('data' => $out[$key], 'class' => $col['class']); + } } return $out; } @@ -487,7 +767,7 @@ class backup_migrate_item { /** * Get the rendered action links for a destination. */ - function get_actions() { + public function get_actions() { $links = $this->get_action_links(); return implode("   ", $links); } @@ -495,118 +775,133 @@ class backup_migrate_item { /** * Get the edit form for the item. */ - function edit_form() { + public function edit_form() { $form = array(); $form['item'] = array( '#type' => 'value', '#value' => $this, ); + $name = $this->get('name'); + $form['name'] = array( + "#type" => "textfield", + "#title" => t("!type name", array('!type' => $this->title_singular)), + "#default_value" => empty($name) ? t('Untitled !type', array('!type' => $this->title_singular)) : $name, + "#required" => TRUE, + ); $form['id'] = array( '#type' => 'value', '#value' => $this->get_id(), ); + + $form['machine_name'] = array( + '#type' => 'machine_name', + '#default_value' => $this->get_id(), + '#maxlength' => 255, + '#machine_name' => array( + 'source' => array('name'), + 'exists' => 'backup_migrate_crud_item_exists', + ), + ); + $form['actions'] = array('#prefix' => '
', '#suffix' => '
', '#weight' => 99); $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save !type', array('!type' => t($this->singular)))); - $form['actions']['cancel'] = array('#markup' => l(t('Cancel'), BACKUP_MIGRATE_MENU_PATH . '/destination')); + $form['actions']['cancel'] = array('#value' => l(t('Cancel'), $this->get_settings_path())); return $form; } /** * Validate the edit form for the item. */ - function edit_form_validate($form, &$form_state) { + public function edit_form_validate($form, &$form_state) { } /** * Submit the edit form for the item. */ - function edit_form_submit($form, &$form_state) { + public function edit_form_submit($form, &$form_state) { $this->from_array($form_state['values']); $this->save(); - _backup_migrate_message('!type saved', array('!type' => t(ucwords($this->singular)))); + _backup_migrate_message('Your !type was saved', array('!type' => t($this->singular))); } /** - * Get the message to send to the user when confirming the deletion of the item. + * The message to send to the user when confirming the deletion of the item. */ - function delete_confirm_message() { - return t('Are you sure you want to delete this !type?', array('!type' => t($item->singular))); + public function delete_confirm_message() { + return t('Are you sure you want to delete the !type %name?', array('!type' => t($this->singular), '%name' => $this->get('name'))); } - /* Static Functions */ - - /** - * This function is not supposed to be called. It is just here to help the po extractor out. + /** + * The message to send to the user when confirming the deletion of the item. */ - function strings() { - // Help the pot extractor find these strings. - t('List !type'); - t('Create !type'); - t('Delete !type'); - t('Edit !type'); - t('Export !type'); + public function revert_confirm_message() { + return t('Are you sure you want to revert the !type %name back to the default settings?', array('!type' => t($this->singular), '%name' => $this->get('name'))); } /** * Get the menu items for manipulating this type. */ - function get_menu_items() { + public function get_menu_items() { + $path = $this->get_settings_path(); + $type = $this->type_name; - $items[BACKUP_MIGRATE_MENU_PATH . '/' . $type] = array( - 'title' => ucwords($this->plural), + $items[$path] = array( + 'title' => $this->title_plural, 'page callback' => 'backup_migrate_menu_callback', - 'page arguments' => array('crud', 'backup_migrate_crud_ui_list', TRUE), + 'page arguments' => array('crud', 'backup_migrate_crud_ui_list', TRUE, $this->type_name), 'access arguments' => array('administer backup and migrate'), 'weight' => 2, 'type' => MENU_LOCAL_TASK, ); - $items[BACKUP_MIGRATE_MENU_PATH . '/' . $type .'/list'] = array( + $items[$path . '/list'] = array( 'title' => 'List !type', - 'title arguments' => array('!type' => t(ucwords($this->plural))), + 'title arguments' => array('!type' => t($this->title_plural)), 'weight' => 1, 'type' => MENU_DEFAULT_LOCAL_TASK, ); - $items[BACKUP_MIGRATE_MENU_PATH . '/' . $type .'/list/add'] = array( + $items[$path . '/add'] = array( 'title' => 'Add !type', - 'title arguments' => array('!type' => t(ucwords($this->singular))), + 'title arguments' => array('!type' => t($this->title_singular)), 'page callback' => 'backup_migrate_menu_callback', - 'page arguments' => array('crud', 'backup_migrate_crud_ui_create', TRUE), + 'page arguments' => array('crud', 'backup_migrate_crud_ui_create', TRUE, $this->type_name), 'access arguments' => array('administer backup and migrate'), 'weight' => 2, 'type' => MENU_LOCAL_ACTION, ); - $items[BACKUP_MIGRATE_MENU_PATH . '/' . $type .'/list/delete'] = array( + $items[$path . '/delete'] = array( 'title' => 'Delete !type', - 'title arguments' => array('!type' => t(ucwords($this->singular))), + 'title arguments' => array('!type' => t($this->title_singular)), 'page callback' => 'backup_migrate_menu_callback', - 'page arguments' => array('crud', 'backup_migrate_crud_ui_delete', TRUE), + 'page arguments' => array('crud', 'backup_migrate_crud_ui_delete', TRUE, $this->type_name), 'access arguments' => array('administer backup and migrate'), 'type' => MENU_CALLBACK, ); - $items[BACKUP_MIGRATE_MENU_PATH . '/' . $type .'/list/edit'] = array( + $items[$path . '/edit'] = array( 'title' => 'Edit !type', - 'title arguments' => array('!type' => t(ucwords($this->singular))), + 'title arguments' => array('!type' => t($this->title_singular)), 'page callback' => 'backup_migrate_menu_callback', - 'page arguments' => array('crud', 'backup_migrate_crud_ui_edit', TRUE), + 'page arguments' => array('crud', 'backup_migrate_crud_ui_edit', TRUE, $this->type_name), 'access arguments' => array('administer backup and migrate'), 'type' => MENU_CALLBACK, ); - $items[BACKUP_MIGRATE_MENU_PATH . '/' . $type .'/list/export'] = array( + $items[$path . '/export'] = array( 'title' => 'Export !type', - 'title arguments' => array('!type' => t(ucwords($this->singular))), + 'title arguments' => array('!type' => t($this->title_singular)), 'page callback' => 'backup_migrate_menu_callback', - 'page arguments' => array('crud', 'backup_migrate_crud_ui_export', TRUE), + 'page arguments' => array('crud', 'backup_migrate_crud_ui_export', TRUE, $this->type_name), 'access arguments' => array('administer backup and migrate'), 'type' => MENU_CALLBACK, ); return $items; } - /** - * Create a new items with the given input. Doesn't load the parameters, but could use them to determine what type to create. + * Create a new items with the given input. + * + * Doesn't load the parameters, but could use them to determine what type to + * create. */ - function create($params = array()) { + public function create($params = array()) { $type = get_class($this); return new $type($params); } @@ -614,14 +909,12 @@ class backup_migrate_item { /** * Get all of the given items. */ - function all_items() { - static $cache = array(); + public function all_items() { + $items = array(); - // Allow other modules to declare destinations programatically. - $items = module_invoke_all($this->db_table); - - // Get any items stored as a variable. This allows destinations to be defined in settings.php - $defaults = (array)variable_get($this->db_table .'_defaults', array()); + // Get any items stored as a variable. This allows destinations to be + // defined in settings.php. + $defaults = (array) variable_get($this->db_table . '_defaults', array()); foreach ($defaults as $info) { if (is_array($info) && $item = $this->create($info)) { $items[$item->get_id()] = $item; @@ -638,9 +931,53 @@ class backup_migrate_item { } } - // Allow other modules to alter the items. This should maybe be before the db override code above - // but then the filters are not able to set defaults for missing values. Other modules should just - // be careful not to overwrite the user's UI changes in an unexpected way. + // Allow other modules to declare destinations programatically. + $default_items = module_invoke_all($this->db_table); + + // Get CTools exported versions. + if (function_exists('ctools_include')) { + ctools_include('export'); + $defaults = ctools_export_load_object($this->db_table); + foreach ($defaults as $info) { + $info = (array) $info; + if (!empty($info) && $item = $this->create($info)) { + // If this item is not already in $default_items, + // set flag to indicate it's only there because of ctools export. + if (empty($default_items[$item->get_id()])) { + $item->settings['ctools_export'] = TRUE; + } + $default_items[$item->get_id()] = $item; + } + } + } + + // Get any items stored as a variable again to correctly mark overrides. + $defaults = (array) variable_get($this->db_table . '_defaults', array()); + foreach ($defaults as $info) { + if (is_array($info) && $item = $this->create($info)) { + $default_items[] = $item; + } + } + + // Add the default items to the array or set the storage flag if they've + // already been overridden. + foreach ($default_items as $item) { + if (isset($items[$item->get_id()])) { + // Items added by ctools export are not overrides. + if (empty($item->settings['ctools_export'])) { + $items[$item->get_id()]->storage = BACKUP_MIGRATE_STORAGE_OVERRIDEN; + } + } + else { + $item->storage = BACKUP_MIGRATE_STORAGE_NONE; + $items[$item->get_id()] = $item; + } + } + + // Allow other modules to alter the items. This should maybe be before the + // db override code above but then the filters are not able to set defaults + // for missing values. Other modules should just be careful not to + // overwrite the user's UI changes in an unexpected way. drupal_alter($this->db_table, $items); return $items; @@ -649,8 +986,17 @@ class backup_migrate_item { /** * A particular item. */ - function item($item_id) { + public function item($item_id) { $items = $this->all_items(); return !empty($items[$item_id]) ? $items[$item_id] : NULL; } -} \ No newline at end of file + + /** + * A particular item. + */ + public function item_exists($item_id) { + $items = $this->all_items(); + return !empty($items[$item_id]); + } + +} diff --git a/sites/all/modules/contrib/admin/backup_migrate/includes/destinations.browser.inc b/sites/all/modules/contrib/admin/backup_migrate/includes/destinations.browser.inc index a33be2a..63ddf21 100644 --- a/sites/all/modules/contrib/admin/backup_migrate/includes/destinations.browser.inc +++ b/sites/all/modules/contrib/admin/backup_migrate/includes/destinations.browser.inc @@ -1,6 +1,5 @@ $file->uri)); backup_migrate_temp_files_add($file->uri); @@ -47,6 +55,7 @@ class backup_migrate_destination_browser_upload extends backup_migrate_destinati } return NULL; } + } /** @@ -55,20 +64,35 @@ class backup_migrate_destination_browser_upload extends backup_migrate_destinati * @ingroup backup_migrate_destinations */ class backup_migrate_destination_browser_download extends backup_migrate_destination_browser { - var $supported_ops = array('manual backup'); - function __construct() { + + /** + * {@inheritdoc} + */ + public $supported_ops = array('manual backup'); + + /** + * Browser downloads must always be the last destination as they must end the + * current process when they are done. + */ + public $weight = 1000; + + /** + * Constructor. + */ + public function __construct() { $params = array(); $params['name'] = "Download"; - $params['destination_id'] = 'download'; + $params['machine_name'] = 'download'; parent::__construct($params); } /** * File save destination callback. */ - function save_file($file, $settings) { - backup_migrate_include('files'); + public function save_file($file, $settings) { + require_once dirname(__FILE__) . '/files.inc'; + $file->transfer(); } -} +} diff --git a/sites/all/modules/contrib/admin/backup_migrate/includes/destinations.db.inc b/sites/all/modules/contrib/admin/backup_migrate/includes/destinations.db.inc index 27d947b..0412404 100644 --- a/sites/all/modules/contrib/admin/backup_migrate/includes/destinations.db.inc +++ b/sites/all/modules/contrib/admin/backup_migrate/includes/destinations.db.inc @@ -1,6 +1,5 @@ set_source($this->get_id()); @@ -40,10 +41,10 @@ class backup_migrate_destination_db extends backup_migrate_destination_remote { /** * Destination configuration callback. */ - function edit_form() { + public function edit_form() { $form = parent::edit_form(); $form['scheme']['#title'] = t('Database type'); -// $form['scheme']['#options'] = array($GLOBALS['db_type'] => $GLOBALS['db_type']); + // $form['scheme']['#options'] = array($GLOBALS['db_type'] => $GLOBALS['db_type']); $form['scheme']['#description'] = t('The type of the database. Drupal only supports one database type at a time, so this must be the same as the current database type.'); $form['path']['#title'] = t('Database name'); $form['path']['#description'] = t('The name of the database. The database must exist, it will not be created for you.'); @@ -54,7 +55,7 @@ class backup_migrate_destination_db extends backup_migrate_destination_remote { /** * Validate the configuration form. Make sure the db info is valid. */ - function edit_form_validate($form, &$form_state) { + public function edit_form_validate($form, &$form_state) { if (!preg_match('/[a-zA-Z0-9_\$]+/', $form_state['values']['path'])) { form_set_error('path', t('The database name is not valid.')); } @@ -62,66 +63,74 @@ class backup_migrate_destination_db extends backup_migrate_destination_remote { } /** - * Get the form for the settings for this destination. + * Get the default settings for this object. * - * Return the default tables whose data can be ignored. These tables mostly contain - * info which can be easily reproducted (such as cache or search index) - * but also tables which can become quite bloated but are not necessarily extremely - * important to back up or migrate during development (such ass access log and watchdog) + * @return array + * The default tables whose data can be ignored. These tables mostly + * contain info which can be easily reproducted (such as cache or search + * index) but also tables which can become quite bloated but are not + * necessarily extremely important to back up or migrate during development + * (such as access log and watchdog). */ - function backup_settings_default() { - $core = array( - 'cache', - 'cache_admin_menu', - 'cache_browscap', - 'cache_content', - 'cache_filter', - 'cache_calendar_ical', - 'cache_location', - 'cache_menu', - 'cache_page', - 'cache_reptag', - 'cache_views', - 'cache_views_data', - 'cache_block', - 'cache_update', - 'cache_form', - 'cache_bootstrap', - 'cache_field', - 'cache_image', - 'cache_path', - 'sessions', - 'search_dataset', - 'search_index', - 'search_keywords_log', - 'search_total', - 'watchdog', - 'accesslog', - 'devel_queries', - 'devel_times', - ); - $nodata_tables = array_merge($core, module_invoke_all('devel_caches')); - return array( - 'nodata_tables' => $nodata_tables, - 'exclude_tables' => array(), + public function backup_settings_default() { + $all_tables = $this->_get_table_names(); + + // Basic modules that should be excluded. + $basic = array( + // Default core tables. + 'accesslog', + 'sessions', + 'watchdog', + // Search module. + 'search_dataset', + 'search_index', + 'search_keywords_log', + 'search_total', + // Devel module. + 'devel_queries', + 'devel_times', + ); + + // Identify all cache tables. + $cache = array('cache'); + foreach ($all_tables as $table_name) { + if (strpos($table_name, 'cache_') === 0) { + $cache[] = $table_name; + } + } + + // Simpletest can create a lot of tables that do not need to be backed up, + // but all of them start with the string 'simpletest' so they can be easily + // excluded. + $simpletest = array(); + foreach ($all_tables as $table_name) { + if (strpos($table_name, 'simpletest') === 0) { + $simpletest[] = $table_name; + } + } + + return array( + 'nodata_tables' => array_merge($basic, $cache, module_invoke_all('devel_caches')), + 'exclude_tables' => $simpletest, 'utils_lock_tables' => FALSE, - ); + ); } /** * Get the form for the backup settings for this destination. */ - function backup_settings_form($settings) { - $tables = $this->get_table_names(); + public function backup_settings_form($settings) { + $objects = $this->get_object_names(); $form['#description'] = t("You may omit specific tables, or specific table data from the backup file. Only omit data that you know you will not need such as cache data, or tables from other applications. Excluding tables can break your Drupal install, so do not change these settings unless you know what you're doing."); $form['exclude_tables'] = array( "#type" => "select", "#multiple" => TRUE, "#title" => t("Exclude the following tables altogether"), - "#options" => $tables, + "#options" => $objects, "#default_value" => $settings['exclude_tables'], "#description" => t("The selected tables will not be added to the backup file."), ); + $tables = $this->get_table_names(); $form['nodata_tables'] = array( "#type" => "select", "#multiple" => TRUE, @@ -142,16 +151,15 @@ class backup_migrate_destination_db extends backup_migrate_destination_remote { /** * Backup from this source. */ - function backup_to_file($file, $settings) { + public function backup_to_file($file, $settings) { $file->push_type($this->get_file_type_id()); backup_migrate_filters_invoke_all('pre_backup', $this, $file, $settings); - //$this->lock_tables($settings); - + // $this->lock_tables($settings); // Switch to a different db if specified. $success = $this->_backup_db_to_file($file, $settings); - //$this->unlock_tables($settings); + // $this->unlock_tables($settings); backup_migrate_filters_invoke_all('post_backup', $this, $file, $settings, $success); return $success ? $file : FALSE; @@ -160,7 +168,7 @@ class backup_migrate_destination_db extends backup_migrate_destination_remote { /** * Restore to this source. */ - function restore_from_file($file, &$settings) { + public function restore_from_file($file, &$settings) { $num = 0; $type = $this->get_file_type_id(); // Open the file using the file wrapper. Check that the dump is of the right type (allow .sql for legacy reasons). @@ -182,7 +190,7 @@ class backup_migrate_destination_db extends backup_migrate_destination_remote { /** * Get the db connection for the specified db. */ - function _get_db_connection() { + public function _get_db_connection() { if (!$this->connection) { $target = $key = ''; $parts = explode(':', $this->get_id()); @@ -195,13 +203,13 @@ class backup_migrate_destination_db extends backup_migrate_destination_remote { else { // If the url is specified build it into a connection info array. if (!empty($this->dest_url)) { - $info = array( - 'driver' => empty($this->dest_url['scheme']) ? NULL : $this->dest_url['scheme'], - 'host' => empty($this->dest_url['host']) ? NULL : $this->dest_url['host'], - 'port' => empty($this->dest_url['port']) ? NULL : $this->dest_url['port'], - 'username' => empty($this->dest_url['user']) ? NULL : $this->dest_url['user'], - 'password' => empty($this->dest_url['pass']) ? NULL : $this->dest_url['pass'], - 'database' => empty($this->dest_url['path']) ? NULL : $this->dest_url['path'], + $info = array( + 'driver' => empty($this->dest_url['scheme']) ? NULL : $this->dest_url['scheme'], + 'host' => empty($this->dest_url['host']) ? NULL : $this->dest_url['host'], + 'port' => empty($this->dest_url['port']) ? NULL : $this->dest_url['port'], + 'username' => empty($this->dest_url['user']) ? NULL : $this->dest_url['user'], + 'password' => empty($this->dest_url['pass']) ? NULL : $this->dest_url['pass'], + 'database' => empty($this->dest_url['path']) ? NULL : $this->dest_url['path'], ); $key = uniqid('backup_migrate_tmp_'); $target = 'default'; @@ -222,21 +230,33 @@ class backup_migrate_destination_db extends backup_migrate_destination_remote { /** * Backup the databases to a file. */ - function _backup_db_to_file($file, $settings) { + public function _backup_db_to_file($file, $settings) { // Must be overridden. } /** * Backup the databases to a file. */ - function _restore_db_from_file($file, $settings) { + public function _restore_db_from_file($file, $settings) { // Must be overridden. } + /** + * Get a list of objects in the database. + */ + public function get_object_names() { + // Must be overridden. + $out = $this->_get_table_names(); + if (method_exists($this, '_get_view_names')) { + $out += $this->_get_view_names(); + } + return $out; + } + /** * Get a list of tables in the database. */ - function get_table_names() { + public function get_table_names() { // Must be overridden. $out = $this->_get_table_names(); return $out; @@ -245,7 +265,7 @@ class backup_migrate_destination_db extends backup_migrate_destination_remote { /** * Get a list of tables in the database. */ - function _get_table_names() { + public function _get_table_names() { // Must be overridden. return array(); } @@ -253,12 +273,12 @@ class backup_migrate_destination_db extends backup_migrate_destination_remote { /** * Lock the database in anticipation of a backup. */ - function lock_tables($settings) { + public function lock_tables($settings) { if ($settings->filters['utils_lock_tables']) { $tables = array(); foreach ($this->get_table_names() as $table) { // There's no need to lock excluded or structure only tables because it doesn't matter if they change. - if (empty($settings->filters['exclude_tables']) || !in_array($table, (array)$settings->filters['exclude_tables'])) { + if (empty($settings->filters['exclude_tables']) || !in_array($table, (array) $settings->filters['exclude_tables'])) { $tables[] = $table; } } @@ -269,14 +289,14 @@ class backup_migrate_destination_db extends backup_migrate_destination_remote { /** * Lock the list of given tables in the database. */ - function _lock_tables($tables) { + public function _lock_tables($tables) { // Must be overridden. } /** * Unlock any tables that have been locked. */ - function unlock_tables($settings) { + public function unlock_tables($settings) { if ($settings->filters['utils_lock_tables']) { $this->_unlock_tables(); } @@ -285,14 +305,15 @@ class backup_migrate_destination_db extends backup_migrate_destination_remote { /** * Unlock the list of given tables in the database. */ - function _unlock_tables($tables) { + public function _unlock_tables($tables) { // Must be overridden. } /** * Get the file type for to backup this destination to. */ - function get_file_type_id() { + public function get_file_type_id() { return 'sql'; } + } diff --git a/sites/all/modules/contrib/admin/backup_migrate/includes/destinations.db.mysql.inc b/sites/all/modules/contrib/admin/backup_migrate/includes/destinations.db.mysql.inc index 019da7b..c783aff 100644 --- a/sites/all/modules/contrib/admin/backup_migrate/includes/destinations.db.mysql.inc +++ b/sites/all/modules/contrib/admin/backup_migrate/includes/destinations.db.mysql.inc @@ -1,6 +1,10 @@ array( "extension" => "sql", @@ -41,17 +69,27 @@ class backup_migrate_destination_db_mysql extends backup_migrate_destination_db /** * Declare any mysql databases defined in the settings.php file as a possible destination. */ - function destinations() { + public function destinations() { $out = array(); global $databases; - foreach ((array)$databases as $db_key => $target) { - foreach ((array)$target as $tgt_key => $info) { + foreach ((array) $databases as $db_key => $target) { + foreach ((array) $target as $tgt_key => $info) { // Only mysql/mysqli supported by this destination. $key = $db_key . ':' . $tgt_key; if ($info['driver'] === 'mysql') { - $url = $info['driver'] . '://' . $info['username'] . ':' . $info['password'] . '@' . $info['host'] . (isset($info['port']) ? ':' . $info['port'] : '') . '/' . $info['database']; + // Compile the database connection string. + $url = 'mysql://'; + $url .= urlencode($info['username']) . ':' . urlencode($info['password']); + $url .= '@'; + $url .= urlencode($info['host']); + if (!empty($info['port'])) { + $url .= ':' . $info['port']; + } + $url .= '/' . urlencode($info['database']); + if ($destination = backup_migrate_create_destination('mysql', array('url' => $url))) { - // Treat the default database differently because it is probably the only one available. + // Treat the default database differently because it is probably + // the only one available. if ($key == 'default:default') { $destination->set_id('db'); $destination->set_name(t('Default Database')); @@ -60,8 +98,8 @@ class backup_migrate_destination_db_mysql extends backup_migrate_destination_db $destination->remove_op('manual backup'); } else { - $destination->set_id('db:'. $key); - $destination->set_name($key .": ". $destination->get_display_location()); + $destination->set_id('db:' . $key); + $destination->set_name($key . ": " . $destination->get_display_location()); } $out[$destination->get_id()] = $destination; } @@ -74,35 +112,34 @@ class backup_migrate_destination_db_mysql extends backup_migrate_destination_db /** * Get the file type for to backup this destination to. */ - function get_file_type_id() { + public function get_file_type_id() { return 'mysql'; } /** * Get the form for the backup settings for this destination. */ - function backup_settings_form($settings) { + public function backup_settings_form($settings) { $form = parent::backup_settings_form($settings); $form['use_mysqldump'] = array( "#type" => "checkbox", "#title" => t("Use mysqldump command"), "#default_value" => !empty($settings['use_mysqldump']), - "#description" => t("Use the mysqldump command line tool if available. This can be faster for large databases but will not work on all servers. EXPERIMENTAL"), + "#description" => t("Use the mysqldump command line tool if available. This can be faster for large databases but will not work on all servers. Also exporting SQL views is not really solid with this option. EXPERIMENTAL"), ); return $form; } - /** * Backup the databases to a file. * * Returns a list of sql commands, one command per line. * That makes it easier to import without loading the whole file into memory. - * The files are a little harder to read, but human-readability is not a priority + * The files are a little harder to read, but human-readability is not a priority. */ - function _backup_db_to_file($file, $settings) { + public function _backup_db_to_file($file, $settings) { if (!empty($settings->filters['use_mysqldump']) && $this->_backup_db_to_file_mysqldump($file, $settings)) { return TRUE; } @@ -113,6 +150,8 @@ class backup_migrate_destination_db_mysql extends backup_migrate_destination_db if ($file->open(TRUE)) { $file->write($this->_get_sql_file_header()); $alltables = $this->_get_tables(); + $allviews = $this->_get_views(); + foreach ($alltables as $table) { if (_backup_migrate_check_timeout()) { return FALSE; @@ -125,6 +164,15 @@ class backup_migrate_destination_db_mysql extends backup_migrate_destination_db } } } + foreach ($allviews as $view) { + if (_backup_migrate_check_timeout()) { + return FALSE; + } + if ($view['name'] && !isset($exclude[$view['name']])) { + $file->write($this->_get_view_create_sql($view)); + } + + } $file->write($this->_get_sql_file_footer()); $file->close(); return $lines; @@ -134,16 +182,14 @@ class backup_migrate_destination_db_mysql extends backup_migrate_destination_db } } - /** * Backup the databases to a file using the mysqldump command. */ - function _backup_db_to_file_mysqldump($file, $settings) { + public function _backup_db_to_file_mysqldump($file, $settings) { $success = FALSE; $nodata_tables = array(); $alltables = $this->_get_tables(); - $command = 'mysqldump --result-file=%file --opt -Q --host=%host --port=%port --user=%user --password=%pass %db'; $args = array( '%file' => $file->filepath(), @@ -155,17 +201,19 @@ class backup_migrate_destination_db_mysql extends backup_migrate_destination_db ); // Ignore the excluded and no-data tables. + $db = $this->dest_url['path']; if (!empty($settings->filters['exclude_tables'])) { - $db = $this->dest_url['path']; - foreach ((array)$settings->filters['exclude_tables'] as $table) { + foreach ((array) $settings->filters['exclude_tables'] as $table) { if (isset($alltables[$table])) { - $command .= ' --ignore-table='. $db .'.'. $table; + $command .= ' --ignore-table=' . $db . '.' . $table; } } - foreach ((array)$settings->filters['nodata_tables'] as $table) { + } + if (!empty($settings->filters['nodata_tables'])) { + foreach ((array) $settings->filters['nodata_tables'] as $table) { if (isset($alltables[$table])) { $nodata_tables[] = $table; - $command .= ' --ignore-table='. $db .'.'. $table; + $command .= ' --ignore-table=' . $db . '.' . $table; } } } @@ -183,10 +231,19 @@ class backup_migrate_destination_db_mysql extends backup_migrate_destination_db /** * Backup the databases to a file. */ - function _restore_db_from_file($file, $settings) { + public function _restore_db_from_file($file, $settings) { $num = 0; if ($file->open() && $conn = $this->_get_db_connection()) { + // Optionally drop all existing tables. + if (!empty($settings->filters['utils_drop_all_tables'])) { + $all_tables = $this->_get_tables(); + $table_names = array_map('backup_migrate_array_name_value', $all_tables); + $table_list = join(', ', $table_names); + $stmt = $conn->prepare("DROP TABLE IF EXISTS $table_list;\n"); + $stmt->execute(); + } + // Read one line at a time and run the query. while ($line = $this->_read_sql_command_from_file($file)) { if (_backup_migrate_check_timeout()) { @@ -209,17 +266,16 @@ class backup_migrate_destination_db_mysql extends backup_migrate_destination_db return $num; } - /** * Read a multiline sql command from a file. * * Supports the formatting created by mysqldump, but won't handle multiline comments. */ - function _read_sql_command_from_file($file) { + public function _read_sql_command_from_file($file) { $out = ''; while ($line = $file->read()) { $first2 = substr($line, 0, 2); - $first3 = substr($line, 0, 2); + $first3 = substr($line, 0, 3); // Ignore single line comments. This function doesn't support multiline comments or inline comments. if ($first2 != '--' && ($first2 != '/*' || $first3 == '/*!')) { @@ -236,46 +292,90 @@ class backup_migrate_destination_db_mysql extends backup_migrate_destination_db /** * Get a list of tables in the database. */ - function _get_table_names() { - $out = array(); - foreach ($this->_get_tables() as $table) { - $out[$table['name']] = $table['name']; + public function _get_table_names() { + if (empty(static::$tableNames)) { + static::$tableNames = $this->query("SHOW FULL TABLES WHERE Table_Type = 'BASE TABLE'") + ->fetchAllKeyed(0, 0); } - return $out; + + return static::$tableNames; + } + + /** + * Get a list of views in the database. + */ + public function _get_view_names() { + if (empty(static::$viewNames)) { + static::$viewNames = $this->query("SHOW FULL TABLES WHERE Table_Type = 'VIEW'") + ->fetchAllKeyed(0, 0); + } + + return static::$viewNames; } /** * Lock the list of given tables in the database. */ - function _lock_tables($tables) { + public function _lock_tables($tables) { if ($tables) { $tables_escaped = array(); foreach ($tables as $table) { - $tables_escaped[] = '`'. db_escape_table($table) .'` WRITE'; + $tables_escaped[] = '`' . db_escape_table($table) . '` WRITE'; } - $this->query('LOCK TABLES '. implode(', ', $tables_escaped)); + $this->query('LOCK TABLES ' . implode(', ', $tables_escaped)); } } /** * Unlock all tables in the database. */ - function _unlock_tables($settings) { + public function _unlock_tables($settings) { $this->query('UNLOCK TABLES'); } + /** + * Get a list of table and view data in the db. + */ + protected function get_table_data() { + if (empty(static::$tableData)) { + $tables = $this->query('SHOW TABLE STATUS') + ->fetchAll(PDO::FETCH_ASSOC); + + foreach ($tables as $table) { + // Lowercase the keys because between Drupal 7.12 and 7.13/14 the + // default query behavior was changed. + // See: http://drupal.org/node/1171866 + $table = array_change_key_case($table); + static::$tableData[$table['name']] = $table; + } + } + + return static::$tableData; + } + /** * Get a list of tables in the db. */ - function _get_tables() { + public function _get_tables() { $out = array(); - // get auto_increment values and names of all tables - $tables = $this->query("show table status", array(), array('fetch' => PDO::FETCH_ASSOC)); - foreach ($tables as $table) { - // Lowercase the keys because between Drupal 7.12 and 7.13/14 the default query behavior was changed. - // See: http://drupal.org/node/1171866 - $table = array_change_key_case($table); - $out[$table['name']] = $table; + foreach ($this->get_table_data() as $table) { + if (!empty($table['engine'])) { + $out[$table['name']] = $table; + } + } + + return $out; + } + + /** + * Get a list of views in the db. + */ + public function _get_views() { + $out = array(); + foreach ($this->get_table_data() as $table) { + if (empty($table['engine'])) { + $out[$table['name']] = $table; + } } return $out; } @@ -283,86 +383,124 @@ class backup_migrate_destination_db_mysql extends backup_migrate_destination_db /** * Get the sql for the structure of the given table. */ - function _get_table_structure_sql($table) { + public function _get_table_structure_sql($table) { $out = ""; - $result = $this->query("SHOW CREATE TABLE `". $table['name'] ."`", array(), array('fetch' => PDO::FETCH_ASSOC)); + $result = $this->query("SHOW CREATE TABLE `" . $table['name'] . "`", array(), array('fetch' => PDO::FETCH_ASSOC)); foreach ($result as $create) { // Lowercase the keys because between Drupal 7.12 and 7.13/14 the default query behavior was changed. // See: http://drupal.org/node/1171866 $create = array_change_key_case($create); - $out .= "DROP TABLE IF EXISTS `". $table['name'] ."`;\n"; + $out .= "DROP TABLE IF EXISTS `" . $table['name'] . "`;\n"; // Remove newlines and convert " to ` because PDO seems to convert those for some reason. $out .= strtr($create['create table'], array("\n" => ' ', '"' => '`')); if ($table['auto_increment']) { - $out .= " AUTO_INCREMENT=". $table['auto_increment']; + $out .= " AUTO_INCREMENT=" . $table['auto_increment']; } $out .= ";\n"; } return $out; } - - /** - * Get the sql to insert the data for a given table - */ - function _dump_table_data_sql_to_file($file, $table) { - $rows_per_line = variable_get('backup_migrate_data_rows_per_line', 30); - $bytes_per_line = variable_get('backup_migrate_data_bytes_per_line', 2000); - - $lines = 0; - $data = $this->query("SELECT * FROM `". $table['name'] ."`", array(), array('fetch' => PDO::FETCH_ASSOC)); - $rows = $bytes = 0; - // Escape backslashes, PHP code, special chars + /** + * Get the sql for the structure of the given table. + */ + public function _get_view_create_sql($view) { + $out = ""; + // Switch SQL mode to get rid of "CREATE ALGORITHM..." what requires more permissions + troubles with the DEFINER user. + $sql_mode = $this->query("SELECT @@SESSION.sql_mode")->fetchField(); + $this->query("SET sql_mode = 'ANSI'"); + $result = $this->query("SHOW CREATE VIEW `" . $view['name'] . "`", array(), array('fetch' => PDO::FETCH_ASSOC)); + $this->query("SET SQL_mode = :mode", array(':mode' => $sql_mode)); + foreach ($result as $create) { + $out .= "DROP VIEW IF EXISTS `" . $view['name'] . "`;\n"; + $out .= "SET sql_mode = 'ANSI';\n"; + $out .= strtr($create['Create View'], "\n", " ") . ";\n"; + $out .= "SET sql_mode = '$sql_mode';\n"; + } + return $out; + } + + /** + * Get the sql to insert the data for a given table. + */ + public function _dump_table_data_sql_to_file($file, $table) { + $rows_per_query = variable_get('backup_migrate_data_rows_per_query', BACKUP_MIGRATE_DATA_ROWS_PER_QUERY); + $rows_per_line = variable_get('backup_migrate_data_rows_per_line', BACKUP_MIGRATE_DATA_ROWS_PER_LINE); + $bytes_per_line = variable_get('backup_migrate_data_bytes_per_line', BACKUP_MIGRATE_DATA_BYTES_PER_LINE); + + if (variable_get('backup_migrate_verbose')) { + _backup_migrate_message('Table: %table', array('%table' => $table['name']), 'success'); + } + + // Escape backslashes, PHP code, special chars. $search = array('\\', "'", "\x00", "\x0a", "\x0d", "\x1a"); $replace = array('\\\\', "''", '\0', '\n', '\r', '\Z'); - - $line = array(); - foreach ($data as $row) { - // DB Escape the values. - $items = array(); - foreach ($row as $key => $value) { - $items[] = is_null($value) ? "null" : "'". str_replace($search, $replace, $value) ."'"; + + $lines = 0; + $from = 0; + $args = array('fetch' => PDO::FETCH_ASSOC); + while ($data = $this->query("SELECT * FROM `" . $table['name'] . "`", array(), $args, $from, $rows_per_query)) { + if ($data->rowCount() == 0) { + break; } - - // If there is a row to be added. - if ($items) { - // Start a new line if we need to. - if ($rows == 0) { - $file->write("INSERT INTO `". $table['name'] ."` VALUES "); - $bytes = $rows = 0; + + $rows = $bytes = 0; + + $line = array(); + foreach ($data as $row) { + $from++; + + // DB Escape the values. + $items = array(); + foreach ($row as $key => $value) { + $items[] = is_null($value) ? "null" : "'" . str_replace($search, $replace, $value) . "'"; } - // Otherwise add a comma to end the previous entry. - else { - $file->write(","); - } - - // Write the data itself. - $sql = implode(',', $items); - $file->write('('. $sql .')'); - $bytes += strlen($sql); - $rows++; - - // Finish the last line if we've added enough items - if ($rows >= $rows_per_line || $bytes >= $bytes_per_line) { - $file->write(";\n"); - $lines++; - $bytes = $rows = 0; + + // If there is a row to be added. + if ($items) { + // Start a new line if we need to. + if ($rows == 0) { + $file->write("INSERT INTO `" . $table['name'] . "` VALUES "); + $bytes = $rows = 0; + } + // Otherwise add a comma to end the previous entry. + else { + $file->write(","); + } + + // Write the data itself. + $sql = implode(',', $items); + $file->write('(' . $sql . ')'); + $bytes += strlen($sql); + $rows++; + + // Finish the last line if we've added enough items. + if ($rows >= $rows_per_line || $bytes >= $bytes_per_line) { + $file->write(";\n"); + $lines++; + $bytes = $rows = 0; + } } } + + // Finish any unfinished insert statements. + if ($rows > 0) { + $file->write(";\n"); + $lines++; + } } - // Finish any unfinished insert statements. - if ($rows > 0) { - $file->write(";\n"); - $lines++; + + if (variable_get('backup_migrate_verbose')) { + _backup_migrate_message('Peak memory usage: %mem', array('%mem' => backup_migrate_get_peak_memory_usage() . 'MB'), 'success'); } - + return $lines; } /** * Get the db connection for the specified db. */ - function _get_db_connection() { + public function _get_db_connection() { if (!$this->connection) { $this->connection = parent::_get_db_connection(); // Set the sql mode because the default is ANSI,TRADITIONAL which is not aware of collation or storage engine. @@ -372,11 +510,34 @@ class backup_migrate_destination_db_mysql extends backup_migrate_destination_db } /** - * Run a db query on this destination's db. + * Run a query on this destination's database using Drupal's MySQL engine. + * + * @param string $query + * The query string. + * @param array $args + * Arguments for the query. + * @param array $options + * Options to pass to the query. + * @param int|null $from + * The starting point for the query; when passed will perform a queryRange() + * method instead of a regular query(). + * @param int|null $count + * The number of records to obtain from this query. Will be ignored if the + * $from argument is empty. + * + * @see DatabaseConnection_mysql::query() + * @see DatabaseConnection_mysql::queryRange() */ - function query($query, $args = array(), $options = array()) { + public function query($query, array $args = array(), array $options = array(), $from = NULL, $count = NULL) { if ($conn = $this->_get_db_connection()) { - return $conn->query($query, $args, $options); + // If no $from is passed in, just do a basic query. + if (is_null($from)) { + return $conn->query($query, $args, $options); + } + // The $from variable was passed in, so do a ranged query. + else { + return $conn->queryRange($query, $from, $count, $args, $options); + } } } @@ -384,7 +545,7 @@ class backup_migrate_destination_db_mysql extends backup_migrate_destination_db * The header for the top of the sql dump file. These commands set the connection * character encoding to help prevent encoding conversion issues. */ - function _get_sql_file_header() { + public function _get_sql_file_header() { return "/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; @@ -392,17 +553,17 @@ class backup_migrate_destination_db_mysql extends backup_migrate_destination_db /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE=NO_AUTO_VALUE_ON_ZERO */; -SET NAMES utf8; +SET NAMES utf8mb4; "; } - + /** * The footer of the sql dump file. */ - function _get_sql_file_footer() { + public function _get_sql_file_footer() { return " - + /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; @@ -411,5 +572,5 @@ SET NAMES utf8; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; "; } -} +} diff --git a/sites/all/modules/contrib/admin/backup_migrate/includes/destinations.email.inc b/sites/all/modules/contrib/admin/backup_migrate/includes/destinations.email.inc index 826c427..fd93405 100644 --- a/sites/all/modules/contrib/admin/backup_migrate/includes/destinations.email.inc +++ b/sites/all/modules/contrib/admin/backup_migrate/includes/destinations.email.inc @@ -1,6 +1,5 @@ filepath()); - $max = variable_get('backup_migrate_max_email_size', 20971520); + $max = variable_get('backup_migrate_max_email_size', BACKUP_MIGRATE_MAX_EMAIL_SIZE); if ($size > $max) { _backup_migrate_message('Could not email the file @file because it is @size and Backup and Migrate only supports emailing files smaller than @max.', array('@file' => $file->filename(), '@size' => format_size($size), '@max' => format_size($max)), 'error'); return FALSE; @@ -34,14 +33,14 @@ class backup_migrate_destination_email extends backup_migrate_destination { /** * Get the form for the settings for this filter. */ - function edit_form() { + public function edit_form() { $form = parent::edit_form(); $form['location'] = array( "#type" => "textfield", "#title" => t("Email Address"), "#default_value" => $this->get_location(), "#required" => TRUE, - "#description" => t('Enter the email address to send the backup files to. Make sure the email sever can handle large file attachments'), + "#description" => t('Enter the email address to send the backup files to. Make sure the email server can handle large file attachments'), ); return $form; } @@ -49,11 +48,12 @@ class backup_migrate_destination_email extends backup_migrate_destination { /** * Validate the configuration form. Make sure the email address is valid. */ - function settings_form_validate($values) { + public function settings_form_validate($values) { if (!valid_email_address($values['location'])) { form_set_error('[location]', t('The e-mail address %mail is not valid.', array('%mail' => $form_state['values']['location']))); } } + } /** @@ -72,69 +72,106 @@ class backup_migrate_destination_email extends backup_migrate_destination { * filename and "filename" which is just the filename. */ function _backup_migrate_destination_email_mail_backup($attachment, $to) { - // Send mail + // Send mail. $attach = fread(fopen($attachment->path, "r"), filesize($attachment->path)); $mail = new mime_mail(); $mail->from = variable_get('site_mail', ini_get('sendmail_from')); - $mail->headers = 'Errors-To: [EMAIL='. $mail->from .']'. $mail->from .'[/EMAIL]'; + $mail->headers = 'Errors-To: [EMAIL=' . $mail->from . ']' . $mail->from . '[/EMAIL]'; $mail->to = $to; $mail->subject = t('Database backup from !site: !file', array('!site' => variable_get('site_name', 'drupal'), '!file' => $attachment->filename)); - $mail->body = t('Database backup attached.') ."\n\n"; + $mail->body = t('Database backup attached.') . "\n\n"; - $mail->add_attachment("$attach", $attachment->filename, "Content-Transfer-Encoding: base64 /9j/4AAQSkZJRgABAgEASABIAAD/7QT+UGhvdG9zaG", NULL, TRUE); + $mail->add_attachment($attach, $attachment->filename, "application/octet-stream"); $mail->send(); } +/** + * + */ class mime_mail { - var $parts; - var $to; - var $from; - var $headers; - var $subject; - var $body; + public $parts; + public $to; + public $from; + public $headers; + public $subject; + public $body; - function mime_mail() { + /** + * + */ + public function __construct() { $this->parts = array(); $this->to = ""; $this->from = ""; - $this->headers = ""; + $this->headers = array(); $this->subject = ""; $this->body = ""; } - function add_attachment($message, $name = "", $ctype = "application/octet-stream", $encode = NULL, $attach = FALSE) { + /** + * + */ + public function add_attachment($message, $name, $ctype) { $this->parts[] = array( - "ctype" => $ctype, "message" => $message, - "encode" => $encode, "name" => $name, - "attach" => $attach, + "ctype" => $ctype, ); } - function build_message($part) { - $message = $part["message"]; - $message = chunk_split(base64_encode($message)); - $encoding = "base64"; - $disposition = $part['attach'] ? "Content-Disposition: attachment; filename=$part[name]\n" : ''; - return "Content-Type: ". $part["ctype"] . ($part["name"] ? "; name = \"". $part["name"] ."\"" : "") ."\nContent-Transfer-Encoding: $encoding\n$disposition\n$message\n"; - } + /** + * + */ + public function build_message($part) { + $crlf = "\r\n"; + // See RFC 2184. + $continuation = $crlf . ' '; + $name = $part['name']; + $len = strlen($name); - function build_multipart() { - $boundary = "b". md5(uniqid(time())); - $multipart = "Content-Type: multipart/mixed; boundary = $boundary\n\nThis is a MIME encoded message.\n\n--$boundary"; - for ($i = sizeof($this->parts) - 1; $i >= 0; $i--) { - $multipart .= "\n". $this->build_message($this->parts[$i]) ."--$boundary"; + // RFC 5322 recommends lines of no longer than 78 chars, which in + // this case comes down to filenames of no longer than 64 chars. + if ($len > 64) { + // We want to preserve the time stamp and extension and such. + $head = substr($name, 0, 28); + $tail = substr($name, ($len - 32)); + $name = $head . '___' . $tail; } - return $multipart .= "--\n"; + $message = chunk_split(base64_encode($part["message"]), 70, $crlf); + $disposition = $name ? "Content-Disposition: attachment; {$continuation}filename=\"$name\"$crlf" : ""; + return "Content-Type: " . $part["ctype"] . ($name ? ";{$continuation}name=\"$name\"" : "") . + "{$crlf}Content-Transfer-Encoding: base64$crlf$disposition$crlf$message"; } - function send() { - $mime = ""; - if (!empty($this->from)) $mime .= "From: ". $this->from ."\n"; - if (!empty($this->headers)) $mime .= $this->headers ."\n"; - if (!empty($this->body)) $this->add_attachment($this->body, "", "text/plain"); - $mime .= "MIME-Version: 1.0\n". $this->build_multipart(); - mail($this->to, $this->subject, "", $mime); + /** + * + */ + public function build_multipart($boundary) { + $multipart = "This is a MIME encoded message.\r\n\r\n--$boundary"; + for ($i = count($this->parts) - 1; $i >= 0; $i--) { + $multipart .= "\r\n" . $this->build_message($this->parts[$i]) . "--$boundary"; + } + return $multipart . "--\r\n"; } + + /** + * + */ + public function send() { + $headers = array(); + if (!empty($this->body)) { + $this->add_attachment($this->body, "", "text/plain"); + } + $headers['MIME-Version'] = "1.0"; + $boundary = "b" . md5(uniqid(time())); + $headers['Content-Type'] = "multipart/mixed; boundary=\"$boundary\""; + $message = $this->build_multipart($boundary); + $params = array(); + $params['body'] = $message; + $params['headers'] = $headers; + $params['subject'] = $this->subject; + + drupal_mail('backup_migrate', 'destination_mail', trim($this->to), '', $params, $this->from); + } + } diff --git a/sites/all/modules/contrib/admin/backup_migrate/includes/destinations.file.inc b/sites/all/modules/contrib/admin/backup_migrate/includes/destinations.file.inc index 4cc7e93..c9c495e 100644 --- a/sites/all/modules/contrib/admin/backup_migrate/includes/destinations.file.inc +++ b/sites/all/modules/contrib/admin/backup_migrate/includes/destinations.file.inc @@ -1,6 +1,5 @@ get_location()); + public function get_realpath() { + if ($realpath = drupal_realpath($this->get_location())) { + return $realpath; + } + return $this->get_location(); } /** * File save destination callback. */ - function _save_file($file, $settings) { - if ($dir = $this->get_location()) { - if ($dir = $this->check_dir($dir)) { - $filepath = rtrim($dir, "/") ."/". $file->filename(); - if (file_unmanaged_move($file->filepath(), $filepath)) { - - // chmod, chown and chgrp the file if needed. - if ($chmod = $this->settings('chmod')) { - if (!@drupal_chmod($filepath, octdec($chmod))) { - _backup_migrate_message('Unable to set the file mode for: @file', array('@file' => $filepath), 'error'); - } - } - if ($chgrp = $this->settings('chgrp')) { - if (!@chgrp($filepath, $chgrp)) { - _backup_migrate_message('Unable to set the file group for: @file', array('@file' => $filepath), 'error'); - } - } - return $file; - } - else { - _backup_migrate_message('Unable to save the file to the directory: @dir', array('@dir' => $dir), 'error'); + public function _save_file($file, $settings) { + if ($this->confirm_destination() && $dir = $this->get_location()) { + $filepath = rtrim($dir, "/") . "/" . $file->filename(); + + // Allow files to be overwritten by the filesystem. + $replace_method = $settings->append_timestamp == 2 ? FILE_EXISTS_REPLACE : FILE_EXISTS_RENAME; + + // Copy the file if there are multiple destinations. + if (count($settings->get_destinations()) > 1) { + file_unmanaged_copy($file->filepath(), $filepath, $replace_method); + } + // Otherwise we can move it and save a delete. + else { + file_unmanaged_move($file->filepath(), $filepath, $replace_method); + } + + // chmod, chown and chgrp the file if needed. + if ($chmod = $this->settings('chmod')) { + if (!@drupal_chmod($filepath, octdec($chmod))) { + _backup_migrate_message('Unable to set the file mode for: @file', array('@file' => $filepath), 'error'); } } + if ($chgrp = $this->settings('chgrp')) { + if (!@chgrp($filepath, $chgrp)) { + _backup_migrate_message('Unable to set the file group for: @file', array('@file' => $filepath), 'error'); + } + } + return $file; } } /** * Determine if we can read the given file. */ - function can_read_file($file_id) { + public function can_read_file($file_id) { return $this->op('restore') && is_readable($this->get_filepath($file_id)); } /** * File load destination callback. */ - function load_file($file_id) { + public function load_file($file_id) { $filepath = $this->get_filepath($file_id); if (file_exists($filepath)) { - backup_migrate_include('files'); + require_once dirname(__FILE__) . '/files.inc'; + return new backup_file(array('filepath' => $filepath)); } } + /** + * Get the file object for the given file. + */ + public function get_file($file_id) { + $files = $this->list_files(); + if (isset($files[$file_id])) { + isset($files[$file_id]); + } + return NULL; + } + /** * File list destination callback. */ - function _list_files() { + public function _list_files() { $files = array(); if ($dir = $this->get_realpath()) { if ($handle = @opendir($dir)) { - backup_migrate_include('files'); + require_once dirname(__FILE__) . '/files.inc'; + while (FALSE !== ($file = readdir($handle))) { - $filepath = $dir ."/". $file; - $files[$file] = new backup_file(array('filepath' => $filepath)); + if (substr($file, 0, 1) !== '.') { + $filepath = $dir . "/" . $file; + $files[$file] = new backup_file(array('filepath' => $filepath)); + } } } } @@ -93,7 +117,7 @@ class backup_migrate_destination_files extends backup_migrate_destination { /** * File delete destination callback. */ - function _delete_file($file_id) { + public function _delete_file($file_id) { $filepath = $this->get_filepath($file_id); file_unmanaged_delete($filepath); } @@ -101,9 +125,9 @@ class backup_migrate_destination_files extends backup_migrate_destination { /** * Get the filepath from the given file id. */ - function get_filepath($file_id) { + public function get_filepath($file_id) { if ($dir = $this->get_realpath()) { - $filepath = rtrim($dir, '/') .'/'. $file_id; + $filepath = rtrim($dir, '/') . '/' . $file_id; return $filepath; } return FALSE; @@ -112,7 +136,7 @@ class backup_migrate_destination_files extends backup_migrate_destination { /** * Get the form for the settings for the files destination. */ - function edit_form() { + public function edit_form() { $form = parent::edit_form(); $form['location'] = array( "#type" => "textfield", @@ -152,7 +176,7 @@ class backup_migrate_destination_files extends backup_migrate_destination { /** * Validate the form for the settings for the files destination. */ - function edit_form_validate($form, &$form_state) { + public function edit_form_validate($form, &$form_state) { $values = $form_state['values']; if (isset($values['settings']['chmod']) && !empty($values['settings']['chmod']) && !preg_match('/0?[0-7]{3}/', $values['settings']['chmod'])) { form_set_error('chmod', t('You must enter a valid chmod octal value (e.g. 644 or 0644) in the change mode field, or leave it blank.')); @@ -163,7 +187,7 @@ class backup_migrate_destination_files extends backup_migrate_destination { /** * Submit the form for the settings for the files destination. */ - function edit_form_submit($form, &$form_state) { + public function edit_form_submit($form, &$form_state) { // Add a 0 to the start of a 3 digit file mode to make it proper PHP encoded octal. if (strlen($form_state['values']['settings']['chmod']) == 3) { $form_state['values']['settings']['chmod'] = '0' . $form_state['values']['settings']['chmod']; @@ -171,15 +195,26 @@ class backup_migrate_destination_files extends backup_migrate_destination { parent::edit_form_submit($form, $form_state); } + /** + * Check that a destination is valid. + */ + public function confirm_destination() { + if ($dir = $this->get_location()) { + return $this->check_dir($dir); + } + return FALSE; + } + /** * Prepare the destination directory for the backups. */ - function check_dir($directory) { + public function check_dir($directory) { if (!file_prepare_directory($directory, FILE_CREATE_DIRECTORY)) { // Unable to create destination directory. - _backup_migrate_message("Unable to create or write to the save directory '%directory'. Please check the file permissions that directory and try again.", array('%directory' => $directory), "error"); + _backup_migrate_message("Unable to create or write to the save directory '%directory'. Please check the file permissions of that directory and try again.", array('%directory' => $directory), "error"); return FALSE; } + // If the destination directory is within the webroot, then secure it as best we can. if ($this->dir_in_webroot($directory)) { $directory = $this->check_web_dir($directory); @@ -191,45 +226,60 @@ class backup_migrate_destination_files extends backup_migrate_destination { /** * Check that a web accessible directory has been properly secured, othewise attempt to secure it. */ - function check_web_dir($directory) { - file_create_htaccess($directory, TRUE); - - // Check the user agent to make sure we're not responding to a request from drupal itself. - // That should prevent infinite loops which could be caused by poormanscron in some circumstances. - if (strpos($_SERVER['HTTP_USER_AGENT'], 'Drupal') !== FALSE) { - return FALSE; + public function check_web_dir($directory) { + // Check if the file has already been tested. + if (is_file($directory . '/tested.txt')) { + return $directory; } + else { + file_create_htaccess($directory, TRUE); - // Check to see if the destination is publicly accessible - $test_contents = "this file should not be publicly accessible"; - // Create the the text.txt file if it's not already there. - if (!is_file($directory .'/test.txt') || file_get_contents($directory .'/test.txt') != $test_contents) { - if ($fp = fopen($directory .'/test.txt', 'w')) { - @fputs($fp, $test_contents); - fclose($fp); - } - else { - $message = t("Security notice: Backup and Migrate was unable to write a test text file to the destination directory %directory, and is therefore unable to check the security of the backup destination. Backups to the server will be disabled until the destination becomes writable and secure.", array('%directory' => $directory)); - drupal_set_message($message, "error"); + // Check the user agent to make sure we're not responding to a request from drupal itself. + // That should prevent infinite loops which could be caused by poormanscron in some circumstances. + if (strpos($_SERVER['HTTP_USER_AGENT'], 'Drupal') !== FALSE) { return FALSE; } - } - // Attempt to read the test file via http. This may fail for other reasons, - // so it's not a bullet-proof check. - if ($this->test_file_readable_remotely($directory .'/test.txt', $test_contents)) { - $message = t("Security notice: Backup and Migrate will not save backup files to the server because the destination directory is publicly accessible. If you want to save files to the server, please secure the '%directory' directory", array('%directory' => $directory)); - drupal_set_message($message, "error"); - return FALSE; + // Check to see if the destination is publicly accessible. + $test_contents = "this file should not be publicly accessible"; + // Create the the text.txt file if it's not already there. + if (!is_file($directory . '/test.txt') || file_get_contents($directory . '/test.txt') != $test_contents) { + if ($fp = fopen($directory . '/test.txt', 'w')) { + @fputs($fp, $test_contents); + fclose($fp); + } + else { + $message = t("Security notice: Backup and Migrate was unable to write a test text file to the destination directory %directory, and is therefore unable to check the security of the backup destination. Backups to the server will be disabled until the destination becomes writable and secure.", array('%directory' => $directory)); + drupal_set_message($message, "error"); + return FALSE; + } + } + + // Attempt to read the test file via http. This may fail for other + // reasons, so it's not a bullet-proof check. + if ($this->test_file_readable_remotely($directory . '/test.txt', $test_contents)) { + $message = t("Security notice: Backup and Migrate will not save backup files to the server because the destination directory is publicly accessible. If you want to save files to the server, please secure the '%directory' directory", array('%directory' => $directory)); + drupal_set_message($message, "error"); + unlink($directory . '/test.txt'); + return FALSE; + } + // Directory tested OK, so we mark it as tested. + if ($fp = fopen($directory . '/tested.txt', 'w')) { + $contents = t('The presence of this file indicates that this directory has been tested as safe to use as a destination for Backup and Migrate. If you change the permissions of this directory or change your web server settings, please delete this file so that the directory can be checked again.'); + @fputs($fp, $contents); + fclose($fp); + } + return $directory; } - return $directory; } /** * Check if the given directory is within the webroot and is therefore web accessible. */ - function dir_in_webroot($directory) { - if (strpos(drupal_realpath($directory), realpath($_SERVER['DOCUMENT_ROOT'])) !== FALSE) { + public function dir_in_webroot($directory) { + $real_dir = drupal_realpath($directory); + $real_root = drupal_realpath(DRUPAL_ROOT); + if ($real_dir == $real_root || strpos($real_dir, $real_root . '/') === 0) { return TRUE; } return FALSE; @@ -238,35 +288,52 @@ class backup_migrate_destination_files extends backup_migrate_destination { /** * Check if a file can be read remotely via http. */ - function test_file_readable_remotely($path, $contents) { - $url = file_create_url($path); - // TODO: Proper checking for absolute paths. - $result = drupal_http_request($url); - if (!empty($result->data) && strpos($result->data, $contents) !== FALSE) { - return TRUE; + public function test_file_readable_remotely($directory, $contents) { + $real_dir = drupal_realpath($directory); + $real_root = drupal_realpath(DRUPAL_ROOT); + if ($real_dir && $real_root) { + // Get the root relative path. + $path = substr($real_dir, strlen($real_root)); + + $url = $GLOBALS['base_url'] . str_replace('\\', '/', $path); + $result = drupal_http_request($url); + if (!empty($result->data) && strpos($result->data, $contents) !== FALSE) { + return TRUE; + } } return FALSE; } + } /** * The manual files directory. */ class backup_migrate_destination_files_manual extends backup_migrate_destination_files { - var $supported_ops = array('manual backup', 'restore', 'list files', 'configure', 'delete'); - function __construct($params = array()) { + public $supported_ops = array('manual backup', 'restore', 'list files', 'configure', 'delete'); + + /** + * + */ + public function __construct($params = array()) { $dir = 'private://backup_migrate/manual'; parent::__construct($params + array('location' => $dir, 'name' => t('Manual Backups Directory'))); } + } /** * The scheduled files directory. */ class backup_migrate_destination_files_scheduled extends backup_migrate_destination_files { - var $supported_ops = array('scheduled backup', 'restore', 'list files', 'configure', 'delete'); - function __construct($params = array()) { + public $supported_ops = array('scheduled backup', 'restore', 'list files', 'configure', 'delete'); + + /** + * + */ + public function __construct($params = array()) { $dir = 'private://backup_migrate/scheduled'; parent::__construct($params + array('location' => $dir, 'name' => t('Scheduled Backups Directory'))); } + } diff --git a/sites/all/modules/contrib/admin/backup_migrate/includes/destinations.ftp.inc b/sites/all/modules/contrib/admin/backup_migrate/includes/destinations.ftp.inc index 306b1ba..8b635ac 100644 --- a/sites/all/modules/contrib/admin/backup_migrate/includes/destinations.ftp.inc +++ b/sites/all/modules/contrib/admin/backup_migrate/includes/destinations.ftp.inc @@ -1,6 +1,5 @@ ftp_object(); if (drupal_ftp_file_to_ftp($file->filepath(), $file->filename(), '.', $ftp)) { return $file; @@ -29,8 +28,9 @@ class backup_migrate_destination_ftp extends backup_migrate_destination_remote { /** * Load from the ftp destination. */ - function load_file($file_id) { - backup_migrate_include('files'); + public function load_file($file_id) { + require_once dirname(__FILE__) . '/files.inc'; + $file = new backup_file(array('filename' => $file_id)); $this->ftp_object(); if (drupal_ftp_ftp_to_file($file->filepath(), $file_id, '.', $this->ftp)) { @@ -42,16 +42,20 @@ class backup_migrate_destination_ftp extends backup_migrate_destination_remote { /** * Delete from the ftp destination. */ - function _delete_file($file_id) { + public function _delete_file($file_id) { $this->ftp_object(); drupal_ftp_delete_file($file_id, $this->ftp); } - function _list_files() { - backup_migrate_include('files'); + /** + * + */ + public function _list_files() { + require_once dirname(__FILE__) . '/files.inc'; + $files = array(); $this->ftp_object(); - $ftp_files = drupal_ftp_file_list('.', $this->ftp); + $ftp_files = (array) drupal_ftp_file_list('.', $this->ftp); foreach ($ftp_files as $file) { $files[$file['filename']] = new backup_file($file); } @@ -61,7 +65,7 @@ class backup_migrate_destination_ftp extends backup_migrate_destination_remote { /** * Get the form for the settings for this filter. */ - function edit_form() { + public function edit_form() { $form = parent::edit_form(); $form['scheme']['#type'] = 'value'; $form['scheme']['#value'] = 'ftp'; @@ -80,15 +84,24 @@ class backup_migrate_destination_ftp extends backup_migrate_destination_remote { return $form; } - function set_pasv($value) { - $this->settings['pasv'] = (bool)$value; + /** + * + */ + public function set_pasv($value) { + $this->settings['pasv'] = (bool) $value; } - function get_pasv() { + /** + * + */ + public function get_pasv() { return isset($this->settings['pasv']) ? $this->settings['pasv'] : FALSE; } - function ftp_object() { + /** + * + */ + public function ftp_object() { if (!$this->ftp) { $this->dest_url['port'] = empty($this->dest_url['port']) ? '21' : $this->dest_url['port']; $this->dest_url['pasv'] = $this->get_pasv(); @@ -96,19 +109,18 @@ class backup_migrate_destination_ftp extends backup_migrate_destination_remote { } return $this->ftp; } + } // The FTP code below was taken from the ftp module by Aaron Winborn. - // Inspired by http://www.devarticles.com/c/a/PHP/My-FTP-Wrapper-Class-for-PHP/ // It's been drupalized, however, and most of the bugs from that example have been fixed. -// - winborn 2007-06-22 - 2007-06-28 - +// - winborn 2007-06-22 - 2007-06-28. define('DRUPAL_FTP_FT_DIRECTORY', 0); define('DRUPAL_FTP_FT_FILE', 1); /** - * creates a new ftp object. if any elements of ftp_map are missing, they'll be filled with the server defaults. + * Creates a new ftp object. if any elements of ftp_map are missing, they'll be filled with the server defaults. */ function drupal_ftp_ftp_object($server, $port, $user, $pass, $dir, $pasv) { $ftp = new stdClass(); @@ -124,17 +136,17 @@ function drupal_ftp_ftp_object($server, $port, $user, $pass, $dir, $pasv) { } /** - * The drupal_ftp_connect function + * The drupal_ftp_connect function * This function connects to an FTP server and attempts to change into the directory specified by * the fourth parameter, $directory. */ function drupal_ftp_connect(&$ftp) { - if (is_NULL($ftp)) { + if (is_null($ftp)) { $ftp = drupal_ftp_ftp_object(); } - if (empty($ftp->__conn) && !drupal_ftp_connected($ftp)) { - // Attempt to connect to the remote server + if (!$ftp->__conn && !drupal_ftp_connected($ftp)) { + // Attempt to connect to the remote server. $ftp->__conn = @ftp_connect($ftp->__server, $ftp->__port); if (!$ftp->__conn) { @@ -142,7 +154,7 @@ function drupal_ftp_connect(&$ftp) { return FALSE; } - // Attempt to login to the remote server + // Attempt to login to the remote server. $ftp->__login = @ftp_login($ftp->__conn, $ftp->__user, $ftp->__password); if (!$ftp->__login) { @@ -150,7 +162,7 @@ function drupal_ftp_connect(&$ftp) { return FALSE; } - // Attempt to change into the working directory + // Attempt to change into the working directory. $chdir = @ftp_chdir($ftp->__conn, $ftp->__directory); if (!$chdir) { @@ -158,7 +170,7 @@ function drupal_ftp_connect(&$ftp) { return FALSE; } - // Set PASV - if needed + // Set PASV - if needed. if ($ftp->__pasv) { $pasv = @ftp_pasv($ftp->__conn, TRUE); if (!$pasv) { @@ -168,7 +180,7 @@ function drupal_ftp_connect(&$ftp) { } } - // Everything worked OK, return TRUE + // Everything worked OK, return TRUE. return TRUE; } @@ -179,38 +191,36 @@ function drupal_ftp_connect(&$ftp) { */ function drupal_ftp_connected(&$ftp) { // Attempt to call the ftp_systype to see if the connect - // to the FTP server is still alive and kicking - - if (is_NULL($ftp)) { + // to the FTP server is still alive and kicking. + if (is_null($ftp)) { $ftp = drupal_ftp_ftp_object(); return FALSE; } if (!@ftp_systype($ftp->__conn)) { - // The connection is dead + // The connection is dead. return FALSE; } else { - // The connection is still alive + // The connection is still alive. return TRUE; } } /** - * This function tries to retrieve the contents of a file from the FTP server. + * This function tries to retrieve the contents of a file from the FTP server. * Firstly it changes into the $directory directory, and then attempts to download the file $filename. * The file is saved locally and its contents are returned to the caller of the function. */ function drupal_ftp_ftp_to_file($file, $filename, $directory, &$ftp) { // Change into the remote directory and retrieve the content - // of a file. Once retrieve, return this value to the caller - + // of a file. Once retrieve, return this value to the caller. if (!@drupal_ftp_connect($ftp)) { return FALSE; } // We are now connected, so let's retrieve the file contents. - // Firstly, we change into the directory + // Firstly, we change into the directory. $chdir = @ftp_chdir($ftp->__conn, $directory); if (!$chdir) { @@ -218,7 +228,7 @@ function drupal_ftp_ftp_to_file($file, $filename, $directory, &$ftp) { return FALSE; } - // We have changed into the directory, let's attempt to get the file + // We have changed into the directory, let's attempt to get the file. $fp = @fopen($file, 'wb'); $get_file = @ftp_fget($ftp->__conn, $fp, $filename, FTP_BINARY); fclose($fp); @@ -229,7 +239,7 @@ function drupal_ftp_ftp_to_file($file, $filename, $directory, &$ftp) { _backup_migrate_message('FTP Error: Unable to download file: @filename from @directory', array('@filename' => $filename, '@directory' => $directory), 'error'); return FALSE; } - + return TRUE; } @@ -242,15 +252,15 @@ function drupal_ftp_file_to_ftp($file, $ftp_filename, $ftp_directory, &$ftp) { } if ($source = drupal_realpath($file)) { - // Now we can try to write to the remote file - $complete_filename = $ftp_directory .'/'. $ftp_filename; + // Now we can try to write to the remote file. + $complete_filename = $ftp_directory . '/' . $ftp_filename; $put_file = @ftp_put($ftp->__conn, $complete_filename, $source, FTP_BINARY); if (!$put_file) { _backup_migrate_message('FTP Error: Couldn\'t write to @complete_filename when trying to save file on the ftp server.', array('@complete_filename' => $complete_filename), 'error'); return FALSE; } - // Everything worked OK + // Everything worked OK. return TRUE; } else { @@ -260,19 +270,18 @@ function drupal_ftp_file_to_ftp($file, $ftp_filename, $ftp_directory, &$ftp) { } /** - * The drupal_ftp_change_directory Function + * The drupal_ftp_change_directory Function * This function simply changes into the $directory folder on the FTP server. * If a connection or permission error occurs then _backup_migrate_message() will contain the error message. */ function drupal_ftp_change_directory($directory, &$ftp) { // Switch to another directory on the web server. If we don't - // have permissions then an error will occur - + // have permissions then an error will occur. if (!@drupal_ftp_connect($ftp)) { return FALSE; } - // Try and change into another directory + // Try and change into another directory. $chdir = ftp_chdir($ftp->__conn, $directory); if (!$chdir) { @@ -280,21 +289,20 @@ function drupal_ftp_change_directory($directory, &$ftp) { return FALSE; } else { - // Changing directories worked OK + // Changing directories worked OK. return TRUE; } } /** - * The drupal_ftp_file_list Function + * The drupal_ftp_file_list Function * This function will change into the $directory folder and get a list of files and directories contained in that folder. * This function still needs a lot of work, but should work in most cases. */ function drupal_ftp_file_list($directory, &$ftp) { // This function will attempt to change into the specified // directory and retrieve a list of files as an associative - // array. This list will include file name, size and date last modified - + // array. This list will include file name, size and date last modified. $file_array = array(); // Can we switch to the desired directory? @@ -306,14 +314,14 @@ function drupal_ftp_file_list($directory, &$ftp) { // This is slower than parsing the raw return values, but it is faster. $file_list = ftp_nlist($ftp->__conn, $directory); - // Save the list of files + // Save the list of files. if (@is_array($file_list)) { - // Interate through the array + // Interate through the array. foreach ($file_list as $file) { $file_array[] = array( 'filename' => $file, - 'filesize' => ftp_size($ftp->__conn, $directory ."/". $file), - 'filetime' => ftp_mdtm($ftp->__conn, $directory ."/". $file), + 'filesize' => ftp_size($ftp->__conn, $directory . "/" . $file), + 'filetime' => ftp_mdtm($ftp->__conn, $directory . "/" . $file), ); } } @@ -322,13 +330,12 @@ function drupal_ftp_file_list($directory, &$ftp) { } /** - * The drupal_ftp_create_directory Function + * The drupal_ftp_create_directory Function * This function tries to make a new directory called $folder_name on the FTP server. * If it can create the folder, then the folder is given appropriate rights with the CHMOD command. */ function drupal_ftp_create_directory($folder_name, &$ftp) { - // Makes a new folder on the web server via FTP - + // Makes a new folder on the web server via FTP. if (!@drupal_ftp_connect($ftp)) { return FALSE; } @@ -337,7 +344,7 @@ function drupal_ftp_create_directory($folder_name, &$ftp) { if ($create_result == TRUE) { // Can we change the files permissions? - $exec_result = @ftp_site($ftp->__conn, 'chmod 0777 '. $folder_name .'/'); + $exec_result = @ftp_site($ftp->__conn, 'chmod 0777 ' . $folder_name . '/'); if ($exec_result == TRUE) { return TRUE; @@ -354,11 +361,11 @@ function drupal_ftp_create_directory($folder_name, &$ftp) { } /** - * The drupal_ftp_delete_file Function + * The drupal_ftp_delete_file Function * This function attempts to delete a file called $filename from the FTP server. */ function drupal_ftp_delete_file($filename, &$ftp) { - // Remove the specified file from the FTP server + // Remove the specified file from the FTP server. if (!@drupal_ftp_connect($ftp)) { return FALSE; } @@ -366,18 +373,18 @@ function drupal_ftp_delete_file($filename, &$ftp) { $delete_result = @ftp_delete($ftp->__conn, $filename); if ($delete_result == TRUE) { - // The file/folder was renamed successfully + // The file/folder was renamed successfully. return TRUE; } else { - // Couldn't delete the selected file + // Couldn't delete the selected file. _backup_migrate_message('FTP Error: Couldn\'t delete the selected file: @filename', array('@filename' => $filename), 'error'); return FALSE; } } /** - * The drupal_ftp_delete_folder Function + * The drupal_ftp_delete_folder Function * This function was one of the hardest to write. It recursively deletes all files and folders from a directory called $folder_name. */ function drupal_ftp_delete_folder($folder_name, &$ftp) { @@ -394,18 +401,18 @@ function drupal_ftp_delete_folder($folder_name, &$ftp) { $file_counter = 0; $content = @ftp_nlist($ftp->__conn, "."); - for ($i = 0; $i < sizeof($content); $i++) { + for ($i = 0; $i < count($content); $i++) { // If we can change into this then it's a directory. - // If not, it's a file + // If not, it's a file. if ($content[$i] != "." && $content[$i] != "..") { if (@ftp_chdir($ftp->__conn, $content[$i])) { - // We have a directory + // We have a directory. $directories[] = $content[$i]; $dir_counter++; @ftp_cdup($ftp->__conn); } else { - // We have a file + // We have a file. $files[] = $content[$i]; $file_counter++; } @@ -417,10 +424,10 @@ function drupal_ftp_delete_folder($folder_name, &$ftp) { } for ($j = 0; $j < $dir_counter; $j++) { - if ($directories[$j] != "." OR $directories[$j] != "..") { + if ($directories[$j] != "." or $directories[$j] != "..") { $location = ftp_pwd($ftp->__conn); drupal_ftp_delete_folder($directories[$j], $ftp); - @ftp_cdup ($ftp->__conn); + @ftp_cdup($ftp->__conn); @ftp_rmdir($ftp->__conn, $directories[$j]); } } diff --git a/sites/all/modules/contrib/admin/backup_migrate/includes/destinations.inc b/sites/all/modules/contrib/admin/backup_migrate/includes/destinations.inc index 1a9af3e..172b5f3 100644 --- a/sites/all/modules/contrib/admin/backup_migrate/includes/destinations.inc +++ b/sites/all/modules/contrib/admin/backup_migrate/includes/destinations.inc @@ -1,91 +1,80 @@ array( - 'description' => t('Save the backup files to any directory on the server which the web-server can write to.'), - 'file' => drupal_get_path('module', 'backup_migrate') .'/includes/destinations.file.inc', + 'description' => t('Save the backup files to any directory on this server which the web-server can write to.'), + 'file' => drupal_get_path('module', 'backup_migrate') . '/includes/destinations.file.inc', 'class' => 'backup_migrate_destination_files', 'type_name' => t('Server Directory'), + 'local' => TRUE, 'can_create' => TRUE, ), 'file_manual' => array( - 'file' => drupal_get_path('module', 'backup_migrate') .'/includes/destinations.file.inc', + 'file' => drupal_get_path('module', 'backup_migrate') . '/includes/destinations.file.inc', 'type_name' => t('Server Directory'), 'class' => 'backup_migrate_destination_files_manual', ), 'file_scheduled' => array( - 'file' => drupal_get_path('module', 'backup_migrate') .'/includes/destinations.file.inc', + 'file' => drupal_get_path('module', 'backup_migrate') . '/includes/destinations.file.inc', 'type_name' => t('Server Directory'), 'class' => 'backup_migrate_destination_files_scheduled', ), ); - } + } $out += array( 'browser_download' => array( - 'file' => drupal_get_path('module', 'backup_migrate') .'/includes/destinations.browser.inc', + 'file' => drupal_get_path('module', 'backup_migrate') . '/includes/destinations.browser.inc', 'class' => 'backup_migrate_destination_browser_download', ), 'browser_upload' => array( - 'file' => drupal_get_path('module', 'backup_migrate') .'/includes/destinations.browser.inc', + 'file' => drupal_get_path('module', 'backup_migrate') . '/includes/destinations.browser.inc', 'class' => 'backup_migrate_destination_browser_upload', ), - 'db' => array( - 'type_name' => t('Database'), - 'description' => t('Import the backup directly into another database. Database destinations can also be used as a source to backup from.'), - 'file' => drupal_get_path('module', 'backup_migrate') .'/includes/destinations.db.inc', - 'class' => 'backup_migrate_destination_db', - 'can_create' => FALSE, - ), - 'mysql' => array( - 'type_name' => t('MySQL Database'), - 'description' => t('Import the backup directly into another MySQL database. Database destinations can also be used as a source to backup from.'), - 'file' => drupal_get_path('module', 'backup_migrate') .'/includes/destinations.db.mysql.inc', - 'class' => 'backup_migrate_destination_db_mysql', - 'can_create' => TRUE, - ), 'ftp' => array( 'description' => t('Save the backup files to any a directory on an FTP server.'), - 'file' => drupal_get_path('module', 'backup_migrate') .'/includes/destinations.ftp.inc', + 'file' => drupal_get_path('module', 'backup_migrate') . '/includes/destinations.ftp.inc', 'class' => 'backup_migrate_destination_ftp', 'type_name' => t('FTP Directory'), 'can_create' => TRUE, + 'remote' => TRUE, ), 's3' => array( - 'description' => t('Save the backup files to a bucket on your !link.', array('!link' => l(t('Amazon S3 account'), 'http://aws.amazon.com/s3/'))), - 'file' => drupal_get_path('module', 'backup_migrate') .'/includes/destinations.s3.inc', + 'description' => t('Save the backup files to a bucket on your Amazon S3 account.', array('@link' => url('http://aws.amazon.com/s3/'))), + 'file' => drupal_get_path('module', 'backup_migrate') . '/includes/destinations.s3.inc', 'class' => 'backup_migrate_destination_s3', 'type_name' => t('Amazon S3 Bucket'), 'can_create' => TRUE, + 'remote' => TRUE, ), 'email' => array( 'type_name' => t('Email'), 'description' => t('Send the backup as an email attachment to the specified email address.'), - 'file' => drupal_get_path('module', 'backup_migrate') .'/includes/destinations.email.inc', + 'file' => drupal_get_path('module', 'backup_migrate') . '/includes/destinations.email.inc', 'class' => 'backup_migrate_destination_email', 'can_create' => TRUE, + 'remote' => TRUE, ), ); @@ -93,18 +82,19 @@ function backup_migrate_backup_migrate_destination_types() { } /** - * Implementation of hook_backup_migrate_destinations(). - * - * Get the built in backup destinations and those in the db. + * Implements hook_backup_migrate_destinations(). */ function backup_migrate_backup_migrate_destinations() { - $out = array(); + require_once dirname(__FILE__) . '/filters.inc'; + + // Get the built in backup destinations and those in the db. + $out = array(); // Add the default, out of the box destinations for new users. if (variable_get('backup_migrate_allow_backup_to_file', TRUE)) { if (variable_get('file_private_path', FALSE)) { - $out['manual'] = backup_migrate_create_destination('file_manual', array('destination_id' => 'manual')); - $out['scheduled'] = backup_migrate_create_destination('file_scheduled', array('destination_id' => 'scheduled')); + $out['manual'] = backup_migrate_create_destination('file_manual', array('machine_name' => 'manual')); + $out['scheduled'] = backup_migrate_create_destination('file_scheduled', array('machine_name' => 'scheduled')); } else { _backup_migrate_message('You must specify a private file system path in the !settings to backup to the server.', array('!settings' => l(t('file system settings'), 'admin/config/media/file-system')), 'warning'); @@ -112,37 +102,13 @@ function backup_migrate_backup_migrate_destinations() { } // Add the browser destination for downloading to the desktop. if (variable_get('backup_migrate_allow_backup_to_download', TRUE)) { - $out['download'] = backup_migrate_create_destination('browser_download'); + $out['download'] = backup_migrate_create_destination('browser_download'); } - $out['upload'] = backup_migrate_create_destination('browser_upload'); + $out['upload'] = backup_migrate_create_destination('browser_upload'); // Expose the configured databases as sources. - backup_migrate_include('filters'); - $out += backup_migrate_filters_invoke_all('destinations'); -/* - global $db_url; - $urls = is_array($db_url) ? $db_url : array('default' => $db_url); - foreach ((array)$urls as $key => $url) { - // Only mysql is currently supported. If more DBMs's are implemented, theis will need to be abstacted. - if (strpos($url, 'mysql') === 0) { - if ($destination = backup_migrate_create_destination('mysql', array('url' => $url))) { - // Treat the default database differently because it is probably the only one available. - if ($key == 'default') { - $destination->set_id('db'); - $destination->set_name(t('Default Database')); - // Dissalow backing up to the default database because that's confusing and potentially dangerous. - unset($destination->supported_ops['scheduled backup']); - unset($destination->supported_ops['manual backup']); - } - else { - $destination->set_id('db:'. $key); - $destination->set_name($key .": ". $destination->get_display_location()); - } - $out[$destination->get_id()] = $destination; - } - } - } -*/ + $out += backup_migrate_filters_invoke_all('destinations'); + return $out; } @@ -150,9 +116,9 @@ function backup_migrate_backup_migrate_destinations() { * Get all the available backup destination. * * @param $op - * The operation which will be performed on the destination. Hooks can use this - * to return only those destinations appropriate for the given op. - * Options include: + * The operation which will be performed on the destination. Hooks can use this + * to return only those destinations appropriate for the given op. + * Options include: * 'manual backup' - destinations available for manual backup * 'scheduled backup' - destinations available for schedules backup * 'list files' - destinations whose backup files can be listed @@ -160,7 +126,7 @@ function backup_migrate_backup_migrate_destinations() { * 'all' - all available destinations should be returned */ function backup_migrate_get_destinations($op = 'all') { - static $destinations = NULL; + $destinations = &drupal_static('backup_migrate_get_destinations', NULL); // Get the list of destinations and cache them locally. if ($destinations === NULL) { @@ -194,10 +160,8 @@ function backup_migrate_get_destination($id) { * Create a destination object of the given type with the given params. */ function backup_migrate_create_destination($destination_type, $params = array()) { - $params['type'] = $destination_type; - // Create a new dummy destination to call the create method on because the base item create is not static. - $destination = new backup_migrate_destination(); - return $destination->create($params); + $params['subtype'] = $destination_type; + return backup_migrate_crud_create_item('destination', $params); } /** @@ -210,6 +174,28 @@ function backup_migrate_destination_get_file($destination_id, $file_id) { return NULL; } +/** + * Load a file from a destination and return the file info. + */ +function backup_migrate_destination_get_latest_file($destination_id) { + $out = NULL; + if ($destination = backup_migrate_get_destination($destination_id)) { + $files = $destination->list_files(); + $max = 0; + foreach ((array) $files as $file) { + $info = $file->info(); + + // If there's a datestamp, it should override the filetime as it's probably more reliable. + $time = !empty($info['datestamp']) ? $info['datestamp'] : $info['filetime']; + if ($time > $max) { + $max = $time; + $out = $file; + } + } + } + return $out; +} + /** * Check if a file exists in the given destination. */ @@ -223,12 +209,36 @@ function backup_migrate_destination_file_exists($destination_id, $file_id) { /** * Send a file to the destination specified by the settings array. */ -function backup_migrate_destination_save_file($file, &$settings) { - if ($destination = $settings->get_destination()) { - $file = $destination->save_file($file, $settings); - return $file; +function backup_migrate_destination_confirm_destination(&$settings) { + if ($destinations = $settings->get_destinations()) { + foreach ($destinations as $key => $destination) { + // Check that the destination is ready to go. + if (!$destination->confirm_destination()) { + unset($destinations[$key]); + } + } } - return NULL; + $settings->destinations = $destinations; + return count($destinations); +} + +/** + * Send a file to the destination specified by the settings array. + */ +function backup_migrate_destination_save_file($file, &$settings) { + $saved_to = array(); + if ($destinations = $settings->get_destinations()) { + foreach ($destinations as $destination) { + // Make sure the file only gets saved to each destination once. + $id = $destination->get('id'); + if (!in_array($id, $saved_to)) { + if ($destination->save_file($file, $settings)) { + $saved_to[] = $id; + } + } + } + } + return $saved_to ? $file : FALSE; } /** @@ -257,75 +267,281 @@ function _backup_migrate_destination_get_file_links($destination_id, $file_id) { * List the backup files in the given destination. */ function backup_migrate_ui_destination_display_files($destination_id = NULL) { - drupal_add_css(drupal_get_path('module', 'backup_migrate') .'/backup_migrate.css'); + drupal_add_css(drupal_get_path('module', 'backup_migrate') . '/backup_migrate.css'); $rows = $sort = array(); if ($destination = backup_migrate_get_destination($destination_id)) { + drupal_set_title(t('@title Files', array('@title' => $destination->get_name()))); + return _backup_migrate_ui_destination_display_files($destination, 20, TRUE); + } + drupal_goto(BACKUP_MIGRATE_MENU_PATH . "/destination"); +} + +/** + * List the backup files in the given destination. + */ +function _backup_migrate_ui_destination_display_files($destination = NULL, $limit = NULL, $show_pager = FALSE) { + if ($destination) { // Refresh the file listing cache if requested. if (isset($_GET['refresh'])) { $destination->file_cache_clear(); drupal_goto($_GET['q']); } + $files = $destination->list_files(); - drupal_set_title(t('@title Files', array('@title' => $destination->get_name()))); + $fetch = $out = ''; + + // Get the fetch link. + if ($destination->cache_files && $destination->fetch_time) { + $fetch = '
' . t('This listing was fetched !time ago. !refresh', array('!time' => format_interval(time() - $destination->fetch_time, 1), '!refresh' => l(t('fetch now'), $_GET['q'], array('query' => array('refresh' => 'true'))))) . '
'; + } + + $out .= $fetch; + $out .= _backup_migrate_ui_destination_display_file_list($files, array('limit' => 20, 'pager' => TRUE)); + $out .= $fetch; + + return $out; + } +} + +/** + * List the backup files in the given destination. + */ +function _backup_migrate_ui_destination_display_file_list($files, $options = array()) { + drupal_add_css(drupal_get_path('module', 'backup_migrate') . '/backup_migrate.css'); + + // Set some default options. + $options += array( + 'pager' => TRUE, + 'more' => FALSE, + 'operations' => TRUE, + 'limit' => NULL, + 'form_elements' => NULL, + ); + + $rows = $sort = array(); + if ($files) { $headers = array( - array('data' => t('Filename'), 'field' => 'filename'), - array('data' => t('Date'), 'field' => 'filetime'), - array('data' => t('Age'), 'field' => 'filetime', 'sort' => 'desc'), - array('data' => t('Size'), 'field' => 'filesize'), + array('data' => 'Filename', 'field' => 'filename'), + array('data' => 'Created', 'field' => 'filetime', 'sort' => 'desc'), + array('data' => 'Size', 'field' => 'filesize'), + ); + if ($options['operations']) { + $headers[] = array('data' => 'Operations'); + } + if ($options['form_elements']) { + array_unshift($headers, ''); + } + + $sort_order = tablesort_get_order($headers); + $sort_key = $sort_order['sql'] ? $sort_order['sql'] : 'filetime'; + $sort_dir = tablesort_get_sort($headers) == 'desc' ? SORT_DESC : SORT_ASC; + + $i = 0; + foreach ((array) $files as $id => $file) { + $info = $file->info(); + + // If there's a datestamp, it should override the filetime as it's probably more reliable. + $info['filetime'] = !empty($info['datestamp']) ? $info['datestamp'] : $info['filetime']; + + $description = ''; + + // Add the description as a new row. + if (!empty($info['description'])) { + $description .= '
' . check_plain($info['description']) . '
'; + } + // Add the backup source. + if (!empty($info['bam_sourcename'])) { + $description .= '
' . t('Source:') . ' ' . check_plain($info['bam_sourcename']) . '
'; + } + // Add the tags as a new row. + if (!empty($info['tags'])) { + $tags = check_plain(implode(', ', (array) $info['tags'])); + $description .= '
' . t('Tags:') . ' ' . $tags . '
'; + } + // Add the other info. + if (!empty($info['bam_other_safe'])) { + foreach ($info['bam_other_safe'] as $label => $data) { + $description .= '
' . $label . ' ' . $data . '
'; + } + } + + // Show only files that can be restored from. + $sort[] = $info[$sort_key]; + $row = array( + check_plain($info['filename']) . $description, + t('!time ago', array( + '!time' => format_interval(time() - $info['filetime'], 2), + )) + . '
' + . format_date($info['filetime'], 'small') + . '
', + format_size($info['filesize']), + ); + if ($options['operations']) { + $row[] = array( + 'data' => implode('   ', $file->destination->get_file_links($file->file_id())), + 'class' => 'backup-migrate-actions', + ); + } + if (isset($options['form_elements'][$id])) { + array_unshift($row, $options['form_elements'][$id]); + } + + $rows[] = $row; + + } + + array_multisort($sort, $sort_dir, $rows); + + // Show only some of them if it's limited. + $showing = $pager = $more = ''; + if ($options['limit']) { + $limit = $options['limit']; + $total = count($rows); + $end = $limit; + $start = 0; + + if ($options['pager']) { + $page = isset($_GET['page']) ? intval($_GET['page']) : 0; + $start = $page * $limit; + + $element = 0; + $GLOBALS['pager_total'][$element] = ceil($total / $limit); + $GLOBALS['pager_page_array'][$element] = $page; + $tags = array( + t('« newest'), + t('« newer'), + '', + t('older »'), + t('oldest »'), + ); + $pager = theme('pager', $tags, $limit, $element, array(), ceil($total / $limit)); + + $end = min($total, $start + $limit); + } + if ($total > $limit && $options['more']) { + $more = ' ' . l(t('view all'), $options['more']); + } + $showing = t('Showing @start to @end of @total files.', array( + '@start' => $start + 1, + '@end' => $end, + '@total' => $total, + )); + + // Limit the number of rows shown. + $rows = array_slice($rows, $start, $limit, TRUE); + } + + $out = theme('table', array( + 'header' => $headers, + 'rows' => $rows, + 'class' => 'backup-migrate-listing backup-migrate-listing-files', + )); + $out .= $showing; + $out .= $pager; + $out .= $more; + } + else { + $out = t('There are no backup files to display.'); + } + return $out; +} + +/** + * List the backup files in the given destination. + */ +function _backup_migrate_ui_destination_display_file_list_options($files, $limit = NULL) { + drupal_add_css(drupal_get_path('module', 'backup_migrate') . '/backup_migrate.css'); + + $rows = $sort = array(); + if ($files) { + $headers = array( + '', + array('data' => 'Filename', 'field' => 'filename'), + array('data' => 'Created', 'field' => 'filetime', 'sort' => 'desc'), + array('data' => 'Size', 'field' => 'filesize'), ); $sort_order = tablesort_get_order($headers); $sort_key = $sort_order['sql'] ? $sort_order['sql'] : 'filetime'; $sort_dir = tablesort_get_sort($headers) == 'desc' ? SORT_DESC : SORT_ASC; - $files = $destination->list_files(); - - $i = 0; - $ops = 0; - foreach ((array)$files as $file) { + $i = 0; + foreach ((array) $files as $file) { $info = $file->info(); - $operations = $destination->get_file_links($file->file_id()); + + // If there's a datestamp, it should override the filetime as it's + // probably more reliable. + $info['filetime'] = !empty($info['datestamp']) ? $info['datestamp'] : $info['filetime']; $description = ''; + // Add the description as a new row. if (!empty($info['description'])) { - $description = '
'. $info['description'] .'
'; + $description .= '
' . check_plain($info['description']) . '
'; + } + // Add the backup source. + if (!empty($info['bam_sourcename'])) { + $description .= '
' . t('Source:') . ' ' . check_plain($info['bam_sourcename']) . '
'; + } + // Add the tags as a new row. + if (!empty($info['tags'])) { + $tags = check_plain(implode(', ', (array) $info['tags'])); + $description .= '
' . t('Tags:') . ' ' . $tags . '
'; + } + // Add the other info. + if (!empty($info['bam_other_safe'])) { + foreach ($info['bam_other_safe'] as $label => $data) { + $description .= '
' . $label . ' ' . $data . '
'; + } } // Show only files that can be restored from. - if ($file->is_recognized_type()) { - $sort[] = $info[$sort_key]; - $rows[] = array_merge(array( - check_plain($info['filename']) . $description, - format_date($info['filetime'], 'small'), - format_interval(time() - $info['filetime'], 1), - format_size($info['filesize']), - ), $operations); - } - $ops = max($ops, count($operations)); - } - - // Add the operations if any - if ($ops) { - $headers[] = array('data' => t('Operations'), 'colspan' => $ops); + $sort[] = $info[$sort_key]; + $rows[] = array( + 'radio', + check_plain($info['filename']) . $description, + t('!time ago', array( + '!time' => format_interval(time() - $info['filetime'], 2), + )) + . '
' + . format_date($info['filetime'], 'small') + . '
', + format_size($info['filesize']), + ); } array_multisort($sort, $sort_dir, $rows); - if ($rows) { - $out = theme('table', array('header' => $headers, 'rows' => $rows)); + // Show only some of them if it's limited. + $showing = $pager = ''; + if ($limit) { + $total = count($rows); + $end = $limit; + $start = 0; + + $showing = t('Showing @start to @end of @total files.', array( + '@start' => $start + 1, + '@end' => $end + 1, + '@total' => $total, + )); + + // Limit the number of rows shown. + $rows = array_slice($rows, $start, $limit, TRUE); } - else { - $out = t('There are no backup files to display.'); - } - if ($destination->cache_files && $destination->fetch_time) { - drupal_add_css(drupal_get_path('module', 'backup_migrate') .'/backup_migrate.css'); - $out .= '
'. t('This listing was fetched !time ago. !refresh', array('!time' => format_interval(time() - $destination->fetch_time, 1), '!refresh' => l(t('fetch now'), $_GET['q'], array('query' => array('refresh' => 'true'))))) .'
'; - } - return $out; + + $out = theme('table', array( + 'header' => $headers, + 'rows' => $rows, + 'class' => 'backup-migrate-listing backup-migrate-listing-files', + )); + $out .= $showing; } - drupal_goto(BACKUP_MIGRATE_MENU_PATH . "/destination"); + else { + $out = t('There are no backup files to display.'); + } + return $out; } /** @@ -333,9 +549,11 @@ function backup_migrate_ui_destination_display_files($destination_id = NULL) { */ function backup_migrate_ui_destination_download_file($destination_id = NULL, $file_id = NULL) { if ($file = backup_migrate_destination_get_file($destination_id, $file_id)) { - backup_migrate_include('files'); + require_once dirname(__FILE__) . '/files.inc'; + $file->transfer(); } + drupal_goto(BACKUP_MIGRATE_MENU_PATH); } @@ -346,19 +564,23 @@ function backup_migrate_ui_destination_restore_file($destination_id = NULL, $fil if (backup_migrate_destination_file_exists($destination_id, $file_id)) { return drupal_get_form('backup_migrate_ui_destination_restore_file_confirm', $destination_id, $file_id); } - drupal_goto(user_access('access backup files') ? BACKUP_MIGRATE_MENU_PATH . "/destination/list/files/". $destination_id : BACKUP_MIGRATE_MENU_PATH); + _backup_migrate_message('Cannot restore from the the file: %file_id because it does not exist.', array('%file_id' => $file_id), 'error'); + if ($destination_id && user_access('access backup files')) { + drupal_goto(BACKUP_MIGRATE_MENU_PATH . '/destination/list/files/' . $destination_id); + } + drupal_goto(BACKUP_MIGRATE_MENU_PATH); } /** * Ask confirmation for file restore. */ function backup_migrate_ui_destination_restore_file_confirm($form, &$form_state, $destination_id, $file_id) { - $sources = _backup_migrate_get_destination_form_item_options('source'); + $sources = _backup_migrate_get_source_form_item_options(); if (count($sources) > 1) { $form['source_id'] = array( "#type" => "select", - "#title" => t("Database"), - "#options" => _backup_migrate_get_destination_form_item_options('source'), + "#title" => t("Restore to"), + "#options" => $sources, "#description" => t("Choose the database to restore to. Any database destinations you have created and any databases specified in your settings.php can be restored to."), "#default_value" => 'db', ); @@ -372,7 +594,7 @@ function backup_migrate_ui_destination_restore_file_confirm($form, &$form_state, $form['destination_id'] = array('#type' => 'value', '#value' => $destination_id); $form['file_id'] = array('#type' => 'value', '#value' => $file_id); - $form = confirm_form($form, t('Are you sure you want to restore the database?'), BACKUP_MIGRATE_MENU_PATH . "/destination/list/files/". $destination_id, t('Are you sure you want to restore the database from the backup file %file_id? This will delete some or all of your data and cannot be undone. Always test your backups on a non-production server!', array('%file_id' => $file_id)), t('Restore'), t('Cancel')); + $form = confirm_form($form, t('Are you sure you want to restore the database?'), BACKUP_MIGRATE_MENU_PATH . "/destination/list/files/" . $destination_id, t('Are you sure you want to restore the database from the backup file %file_id? This will delete some or all of your data and cannot be undone. Always test your backups on a non-production server!', array('%file_id' => $file_id)), t('Restore'), t('Cancel')); drupal_set_message(t('Restoring will delete some or all of your data and cannot be undone. Always test your backups on a non-production server!'), 'warning', FALSE); $form = array_merge_recursive($form, backup_migrate_filters_settings_form(backup_migrate_filters_settings_default('restore'), 'restore')); $form['actions']['#weight'] = 100; @@ -381,8 +603,8 @@ function backup_migrate_ui_destination_restore_file_confirm($form, &$form_state, if (@$form['advanced']) { $form['advanced']['#type'] = 'fieldset'; $form['advanced']['#title'] = t('Advanced Options'); - $form['advanced']['#collapsed'] = true; - $form['advanced']['#collapsible'] = true; + $form['advanced']['#collapsed'] = TRUE; + $form['advanced']['#collapsible'] = TRUE; } return $form; @@ -397,7 +619,7 @@ function backup_migrate_ui_destination_restore_file_confirm_submit($form, &$form if ($destination_id && $file_id) { backup_migrate_perform_restore($destination_id, $file_id, $form_state['values']); } - $redir = user_access('access backup files') ? BACKUP_MIGRATE_MENU_PATH . "/destination/list/files/". $destination_id : BACKUP_MIGRATE_MENU_PATH; + $redir = user_access('access backup files') ? BACKUP_MIGRATE_MENU_PATH . "/destination/list/files/" . $destination_id : BACKUP_MIGRATE_MENU_PATH; $form_state['redirect'] = $redir; } @@ -408,6 +630,10 @@ function backup_migrate_ui_destination_delete_file($destination_id = NULL, $file if (backup_migrate_destination_file_exists($destination_id, $file_id)) { return drupal_get_form('backup_migrate_ui_destination_delete_file_confirm', $destination_id, $file_id); } + _backup_migrate_message('Cannot delete the file: %file_id because it does not exist.', array('%file_id' => $file_id), 'error'); + if ($destination_id && user_access('access backup files')) { + drupal_goto(BACKUP_MIGRATE_MENU_PATH . '/destination/list/files/' . $destination_id); + } drupal_goto(BACKUP_MIGRATE_MENU_PATH); } @@ -417,7 +643,7 @@ function backup_migrate_ui_destination_delete_file($destination_id = NULL, $file function backup_migrate_ui_destination_delete_file_confirm($form, &$form_state, $destination_id, $file_id) { $form['destination_id'] = array('#type' => 'value', '#value' => $destination_id); $form['file_id'] = array('#type' => 'value', '#value' => $file_id); - return confirm_form($form, t('Are you sure you want to delete the backup file?'), BACKUP_MIGRATE_MENU_PATH . '/destination/list/files/'. $destination_id, t('Are you sure you want to delete the backup file %file_id? This action cannot be undone.', array('%file_id' => $file_id)), t('Delete'), t('Cancel')); + return confirm_form($form, t('Are you sure you want to delete the backup file?'), BACKUP_MIGRATE_MENU_PATH . '/destination/list/files/' . $destination_id, t('Are you sure you want to delete the backup file %file_id? This action cannot be undone.', array('%file_id' => $file_id)), t('Delete'), t('Cancel')); } /** @@ -430,63 +656,125 @@ function backup_migrate_ui_destination_delete_file_confirm_submit($form, &$form_ backup_migrate_destination_delete_file($destination_id, $file_id); _backup_migrate_message('Database backup file deleted: %file_id', array('%file_id' => $file_id)); } - $form_state['redirect'] = user_access('access backup files') ? BACKUP_MIGRATE_MENU_PATH . "/destination/list/files/". $destination_id : BACKUP_MIGRATE_MENU_PATH; + $form_state['redirect'] = user_access('access backup files') ? BACKUP_MIGRATE_MENU_PATH . "/destination/list/files/" . $destination_id : BACKUP_MIGRATE_MENU_PATH; } /* Utilities */ /** - * Get the source options as a form element. + * Get pulldown to select existing source options. */ -function _backup_migrate_get_source_form($source_id = 'db') { - backup_migrate_include('destinations'); +function _backup_migrate_get_destination_pulldown($op, $destination_id = NULL, $copy_destination_id = NULL) { + drupal_add_js(drupal_get_path('module', 'backup_migrate') . '/backup_migrate.js'); - $form = array(); - $sources = _backup_migrate_get_source_pulldown($source_id); - if (count($sources['#options']) > 1) { - $form['source'] = array( - "#type" => "fieldset", - "#title" => t("Backup Source"), - "#collapsible" => TRUE, - "#collapsed" => FALSE, - "#tree" => FALSE, - ); - $sources['#description'] = t("Choose the database to backup. Any database destinations you have created and any databases specified in your settings.php can be backed up."); + $destinations = _backup_migrate_get_destination_form_item_options($op); + $form = array( + '#element_validate' => array('_backup_migrate_destination_pulldown_validate'), + '#after_build' => array('_backup_migrate_process_destination_pulldown'), + ); + $form['destination_id'] = array( + '#type' => 'select', + '#title' => t('Backup Destination'), + '#options' => $destinations, + '#default_value' => $destination_id, + // '#process' => array('_backup_migrate_process_destination_pulldown'), + ); + if (user_access('administer backup and migrate')) { + $form['destination_id']['#description'] = l(t('Create new destination'), BACKUP_MIGRATE_MENU_PATH . '/settings/destination/add'); + } + $form['copy'] = array( + '#type' => 'checkbox', + '#title' => '' . t('Save a copy to a second destination') . '', + '#default_value' => !empty($copy_destination_id), + ); + $form['copy_destination'] = array( + '#type' => 'backup_migrate_dependent', + '#dependencies' => array( + 'copy' => TRUE, + ), + ); + $form['copy_destination']['copy_destination_id'] = array( + '#type' => 'select', + '#title' => t('Second Backup Destination'), + '#options' => $destinations, + '#default_value' => $copy_destination_id, + ); - $form['source']['source_id'] = $sources; - } - else { - $form = array(); - $form['source']['source_id'] = array( - "#type" => "value", - "#value" => $source_id, - ); - } return $form; } /** - * Get pulldown to select existing source options. + * Return the destination pulldown with an option to save a second copy. */ -function _backup_migrate_get_source_pulldown($source_id = NULL) { - backup_migrate_include('destinations'); - $sources = _backup_migrate_get_destination_form_item_options('source'); - $form = array( - "#type" => "select", - "#title" => t("Backup Source"), - "#options" => _backup_migrate_get_destination_form_item_options('source'), - "#default_value" => $source_id, +function _backup_migrate_process_destination_pulldown($element) { + $id = $element['destination_id']['#id']; + $settings = array( + 'backup_migrate' => array( + 'destination_selectors' => array( + $id => array( + 'destination_selector' => $id, + 'copy_destination_selector' => $element['copy_destination']['copy_destination_id']['#id'], + 'copy' => $element['copy']['#id'], + 'labels' => array( + t('Local') => t('Save an offsite copy to'), + t('Offsite') => t('Save a local copy to'), + ), + ), + ), + ), ); - return $form; + drupal_add_js($settings, 'setting'); + + return $element; +} + +/** + * Validate the destination and second destination widget. + */ +function _backup_migrate_destination_pulldown_validate($element, &$form_state, $form) { + if (empty($element['copy']['#value']) || $element['copy_destination']['copy_destination_id']['#value'] == $element['destination_id']['#value']) { + _backup_migrate_destination_pulldown_set_value($form_state['values'], $element['copy_destination']['copy_destination_id']['#parents'], ''); + } +} + +/** + * Set the value of a form field given it's parent array. + */ +function _backup_migrate_destination_pulldown_set_value(&$values, $parents, $value) { + $key = array_shift($parents); + if (empty($parents)) { + $values[$key] = $value; + } + else { + _backup_migrate_destination_pulldown_set_value($values[$key], $parents, $value); + } } /** * Get the destination options as an options array for a form item. */ function _backup_migrate_get_destination_form_item_options($op) { - $out = array(); + $remote = $local = array(); foreach (backup_migrate_get_destinations($op) as $key => $destination) { - $out[$key] = $destination->get_name(); + // Only work with destinations that are currently configured and will work. + if ($destination->confirm_destination()) { + if ($destination->op('remote backup')) { + $remote[$key] = $destination->get_name(); + } + else { + $local[$key] = $destination->get_name(); + } + } + } + $out = array(); + if ($remote && $local) { + $out = array( + t('Local') => $local, + t('Offsite') => $remote, + ); + } + else { + $out = $remote + $local; } return $out; } @@ -494,23 +782,33 @@ function _backup_migrate_get_destination_form_item_options($op) { /** * A base class for creating destinations. */ -class backup_migrate_destination extends backup_migrate_item { - var $db_table = "backup_migrate_destinations"; - var $type_name = "destination"; - var $default_values = array('settings' => array()); - var $singular = 'destination'; - var $plural = 'destinations'; - var $cache_files = FALSE; - var $fetch_time = NULL; - var $cache_expire = 86400; // 24 hours - - var $destination_type = ""; - var $supported_ops = array(); +class backup_migrate_destination extends backup_migrate_location { + public $db_table = 'backup_migrate_destinations'; + public $type_name = 'destination'; + public $default_values = array('settings' => array()); + public $singular = 'destination'; + public $plural = 'destinations'; + public $title_plural = 'Destinations'; + public $title_singular = 'Destination'; + public $cache_files = FALSE; + public $fetch_time = NULL; + public $weight = 0; + public $destination_type = ''; + public $supported_ops = array(); /** - * This function is not supposed to be called. It is just here to help the po extractor out. + * 24 hours, i.e. 24 * 60 * 60 seconds. + * + * @var int. */ - function strings() { + public $cache_expire = 86400; + + /** + * This function is not supposed to be called. + * + * It is just here to help the po extractor out. + */ + public function strings() { // Help the pot extractor find these strings. t('Destination'); t('Destinations'); @@ -518,79 +816,10 @@ class backup_migrate_destination extends backup_migrate_item { t('destination'); } - function ops() { - return $this->supported_ops; - } - - /** - * Does this destination support the given operation. - */ - function op($op) { - $ops = (array)$this->ops(); - return in_array($op, $ops); - } - - /** - * Remove the given op from the support list. - */ - function remove_op($op) { - $key = array_search($op, $this->supported_ops); - if ($key !== FALSE) { - unset($this->supported_ops[$key]); - } - } - - function get_name() { - return @$this->name; - } - - function set_name($name) { - return $this->name = $name; - } - - function set_type($type) { - $this->type = $type; - } - - function set_location($location) { - $this->location = $location; - } - - function get_location() { - return @$this->location; - } - - function get_display_location() { - return $this->get_location(); - } - - function file_types() { - return array(); - } - - function settings($key = NULL) { - $out = $this->settings; - if ($key) { - $out = isset($out[$key]) ? $out[$key] : NULL; - } - return $out; - } - - /** - * Get the type name of this destination for display to the user. - */ - function get_destination_type_name() { - if ($type = $this->destination_type) { - $types = backup_migrate_get_destination_types(); - return isset($types[$type]['type_name']) ? $types[$type]['type_name'] : $type; - } - } - /** * Save the given file to the destination. */ - function save_file($file, $settings) { - // This must be overriden. + public function save_file($file, $settings) { $this->file_cache_clear(); // Save the file metadata if the destination supports it. @@ -601,75 +830,114 @@ class backup_migrate_destination extends backup_migrate_item { /** * Save the given file to the destination. */ - function _save_file($file, $settings) { + public function _save_file($file, $settings) { // This must be overriden. return $file; } /** - * Save the file metadata + * Save the file metadata. + * + * @return object + * The file object after it has been saved. */ - function save_file_info($file, $settings) { + public function save_file_info($file, $settings) { $info = $this->create_info_file($file); // Save the info file and the actual file. return $this->_save_file($info, $settings); } /** - * Load the file with the given destination specific id and return as a backup_file object. + * Load the file with the given destination specific ID. + * + * @return \backup_file + * A backup file. */ - function load_file($file_id) { + public function load_file($file_id) { // This must be overriden. return NULL; } /** - * Check if a file exists in the given destination. + * Check if the file exists in the list of available files. + * + * Actual destination types may have more efficient ways of doing this. */ - function file_exists($file_id) { - // Check if the file exists in the list of available files. Actual destination types may have more efficient ways of doing this. + public function file_exists($file_id) { $files = $this->list_files(); return isset($files[$file_id]); } /** - * List all the available files in the given destination with their destination specific id. + * List all the available files in the given destination. + * + * Includes their destination specific id. */ - function list_files() { - $out = NULL; + public function list_files() { + $files = NULL; if ($this->cache_files) { - $out = $this->file_cache_get(); + $files = $this->file_cache_get(); } - if ($out === NULL) { - $out = $this->_list_files(); - $out = $this->load_files_info($out); + if ($files === NULL) { + require_once dirname(__FILE__) . '/files.inc'; + + $files = $this->_list_files(); + $files = $this->load_files_info($files); if ($this->cache_files) { - $this->file_cache_set($out); + $this->file_cache_set($files); + } + + // Clean up any previous abandoned tmp files before going further. + _backup_migrate_temp_files_delete(); + } + + $out = array(); + if (is_array($files)) { + foreach ($files as $id => $file) { + if ($file->is_recognized_type()) { + $out[$id] = $file; + $out[$id]->destination = &$this; + } } } return $out; } /** - * List all the available files in the given destination with their destination specific id. + * Count all the available files in the given destination. */ - function _list_files() { + public function count_files() { + return count($this->list_files()); + } + + /** + * List all the available files in the given destination. + * + * Includes their destination specific id. + */ + public function _list_files() { return array(); } /** * Load up the file's metadata from the accompanying .info file if applicable. */ - function load_files_info($files) { + public function load_files_info($files) { foreach ($files as $key => $file) { + // See if there is an info file with the same name as the backup. if (isset($files[$key . '.info'])) { - // See if there is an info file with the same name as the backup. - $info = drupal_parse_info_file($files[$key . '.info']->filepath()); + $info_file = $this->load_file($files[$key . '.info']->file_id()); + $info = drupal_parse_info_file($info_file->filepath()); // Allow the stored metadata to override the detected metadata. + unset($info['filename']); $file->file_info = $info + $file->file_info; - // Remove the metadata file from the list + // Remove the metadata file from the list. unset($files[$key . '.info']); } + + // Add destination specific info. + $file->info_set('destination_id', $this->get('id')); + $file->info_set('remote', $this->get('remote')); } return $files; @@ -678,7 +946,7 @@ class backup_migrate_destination extends backup_migrate_item { /** * Create an ini file and write the meta data. */ - function create_info_file($file) { + public function create_info_file($file) { $info = $this->_file_info_file($file); $data = _backup_migrate_array_to_ini($file->file_info); $info->put_contents($data); @@ -688,7 +956,7 @@ class backup_migrate_destination extends backup_migrate_item { /** * Create the info file object. */ - function _file_info_file($file) { + public function _file_info_file($file) { $info = new backup_file(array('filename' => $this->_file_info_filename($file->file_id()))); return $info; } @@ -696,35 +964,37 @@ class backup_migrate_destination extends backup_migrate_item { /** * Determine the file name of the info file for a file. */ - function _file_info_filename($file_id) { + public function _file_info_filename($file_id) { return $file_id . '.info'; } /** * Cache the file list. */ - function file_cache_set($files) { - cache_set('backup_migrate_file_list:'. $this->get_id(), $files, 'cache', time() + $this->cache_expire); + public function file_cache_set($files) { + cache_set('backup_migrate_file_list:' . $this->get_id(), $files, 'cache', time() + $this->cache_expire); } /** * Retrieve the file list. */ - function file_cache_get() { - backup_migrate_include('files'); - $cache = cache_get('backup_migrate_file_list:'. $this->get_id()); + public function file_cache_get() { + require_once dirname(__FILE__) . '/files.inc'; + + $cache = cache_get('backup_migrate_file_list:' . $this->get_id()); if (!empty($cache->data) && $cache->created > (time() - $this->cache_expire)) { $this->fetch_time = $cache->created; return $cache->data; } $this->fetch_time = 0; + return NULL; } /** * Retrieve the file list. */ - function file_cache_clear() { + public function file_cache_clear() { if ($this->cache_files) { $this->file_cache_set(NULL); } @@ -733,7 +1003,7 @@ class backup_migrate_destination extends backup_migrate_item { /** * Delete the file with the given destination specific id. */ - function delete_file($file_id) { + public function delete_file($file_id) { $this->file_cache_clear(); $this->_delete_file($file_id); $this->_delete_file($this->_file_info_filename($file_id)); @@ -742,41 +1012,70 @@ class backup_migrate_destination extends backup_migrate_item { /** * Delete the file with the given destination specific id. */ - function _delete_file($file_id) { + public function _delete_file($file_id) { // This must be overriden. } /** * Get the edit form for the item. */ - function edit_form() { + public function edit_form() { if (get_class($this) !== 'backup_migrate_destination') { $form = parent::edit_form(); - $form['name'] = array( - "#type" => "textfield", - "#title" => t("Destination name"), - "#default_value" => $this->get_name(), - "#required" => TRUE, - ); - $form['type'] = array( + $form['subtype'] = array( "#type" => "value", - "#default_value" => $this->destination_type, + "#default_value" => $this->get('subtype'), ); } else { - $types = backup_migrate_get_destination_types(); - $items = array(); - // If no (valid) node type has been provided, display a node type overview. + $types = backup_migrate_get_destination_subtypes(); + $items = array( + 'remote' => array( + 'title' => t('Offsite Destinations'), + 'description' => t('For the highest level of protection, set up an offsite backup destination in a location separate from your website.'), + 'items' => array(), + ), + 'local' => array( + 'title' => t('Local Destinations'), + 'description' => t('Local backups are quick and convenient but do not provide the additional safety of offsite backups.'), + 'items' => array(), + ), + 'other' => array( + 'title' => t('Other Destinations'), + 'description' => t('These destinations have not been classified because they were created for Backup and Migrate version 2. They may not work correctly with this version.'), + 'items' => array(), + ), + + ); + + // If no (valid) node type has been provided, display a node type + // overview. + $path = $this->get_settings_path(); foreach ($types as $key => $type) { if (@$type['can_create']) { $type_url_str = str_replace('_', '-', $key); - $out = '
'. l($type['type_name'], BACKUP_MIGRATE_MENU_PATH . "/destination/list/add/$type_url_str", array('attributes' => array('title' => t('Add a new @s destination.', array('@s' => $type['type_name']))))) .'
'; - $out .= '
'. filter_xss_admin($type['description']) .'
'; - $items[] = $out; + $out = '
' . l($type['type_name'], $path . "/add/$type_url_str", array('attributes' => array('title' => t('Add a new @s destination.', array('@s' => $type['type_name']))))) . '
'; + $out .= '
' . filter_xss_admin($type['description']) . '
'; + + if (!empty($type['local'])) { + $items['local']['items'][] = $out; + } + if (!empty($type['remote'])) { + $items['remote']['items'][] = $out; + } + if (empty($type['local']) && empty($type['remote'])) { + $items['other']['items'][] = $out; + } } } - if (count($items)) { - $output = t('Choose the type of destination you would like to create:') .'
'. implode('', $items) .'
'; + if (count($items['local']['items']) || count($items['remote']['items']) || count($items['other']['items'])) { + $output = '

' . t('Choose the type of destination you would like to create:') . '

'; + foreach ($items as $group) { + if (count($group['items'])) { + $group['body'] = '
' . implode('', $group['items']) . '
'; + $output .= theme('backup_migrate_group', $group); + } + } } else { $output = t('No destination types available.'); @@ -790,49 +1089,30 @@ class backup_migrate_destination extends backup_migrate_item { } /** - * Get the message to send to the user when confirming the deletion of the item. + * Get a message to send to the user when confirming the deletion of the item. */ - function delete_confirm_message() { + public function delete_confirm_message() { return t('Are you sure you want to delete the destination %name? Backup files already saved to this destination will not be deleted.', array('%name' => $this->get_name())); } - /** - * Get the columns needed to list the type. - */ - function get_list_column_info() { - $out = parent::get_list_column_info(); - $out = array( - 'name' => array('title' => t('Name')), - 'destination_type_name' => array('title' => t('Type')), - 'display_location' => array('title' => t('Location')), - ) + $out; - return $out; - } - - /** - * Get a row of data to be used in a list of items of this type. - */ - function get_list_row() { - $out = parent::get_list_row(); - - // Supress destinations with no actions as there's no value in showing them (and they may confuse new users). - if (empty($out['actions'])) { - return NULL; - } - return $out; + * Get a boolean representing if the destination is remote or local. + */ + public function get_remote() { + return $this->op('remote backup'); } /** * Get the action links for a destination. */ - function get_action_links() { + public function get_action_links() { $out = parent::get_action_links(); $item_id = $this->get_id(); - // Don't display the download/delete/restore ops if they are not available for this destination. + // Don't display the download/delete/restore ops if they are not available + // for this destination. if ($this->op('list files') && user_access("access backup files")) { - $out = array('list files' => l(t("list files"), BACKUP_MIGRATE_MENU_PATH . "/$this->type_name/list/files/". $item_id)) + $out; + $out = array('list files' => l(t("list files"), $this->get_settings_path() . '/list/files/' . $item_id)) + $out; } if (!$this->op('configure') || !user_access('administer backup and migrate')) { unset($out['edit']); @@ -843,22 +1123,25 @@ class backup_migrate_destination extends backup_migrate_item { /** * Get the action links for a file on a given destination. */ - function get_file_links($file_id) { - $out = array('download' => '', 'restore' => '', 'delete' => ''); + public function get_file_links($file_id) { + $out = array(); - // Don't display the download/delete/restore ops if they are not available for this destination. - $can_read = $this->can_read_file($file_id); - $can_delete = $this->can_delete_file($file_id); + // Don't display the download/delete/restore ops if they are not available + // for this destination. + $can_read = $this->can_read_file($file_id); + $can_delete = $this->can_delete_file($file_id); + + $path = $this->get_settings_path(); $destination_id = $this->get_id(); if ($can_read && user_access("access backup files")) { - $out['download'] = l(t("download"), BACKUP_MIGRATE_MENU_PATH . "/destination/downloadfile/". $destination_id .'/'. $file_id); + $out[] = l(t("download"), $path . '/downloadfile/' . $destination_id . '/' . $file_id); } if ($can_read && user_access("restore from backup")) { - $out['restore'] = l(t("restore"), BACKUP_MIGRATE_MENU_PATH . "/destination/restorefile/". $destination_id .'/'. $file_id); + $out[] = l(t("restore"), $path . '/list/restorefile/' . $destination_id . '/' . $file_id); } if ($can_delete && user_access("delete backup files")) { - $out['delete'] = l(t("delete"), BACKUP_MIGRATE_MENU_PATH . "/destination/deletefile/". $destination_id .'/'. $file_id); + $out[] = l(t("delete"), $path . '/list/deletefile/' . $destination_id . '/' . $file_id); } return $out; } @@ -866,237 +1149,148 @@ class backup_migrate_destination extends backup_migrate_item { /** * Determine if we can read the given file. */ - function can_read_file($file_id) { + public function can_read_file($file_id) { return $this->op('restore'); } /** * Determine if we can read the given file. */ - function can_delete_file($file_id) { + public function can_delete_file($file_id) { return $this->op('delete'); } /** * Get the form for the settings for this destination type. */ - function settings_default() { + public function settings_default() { return array(); } /** * Get the form for the settings for this destination. */ - function settings_form($form) { + public function settings_form($form) { return $form; } /** * Validate the form for the settings for this destination. */ - function settings_form_validate($form_values) { + public function settings_form_validate($form_values) { } /** * Submit the settings form. Any values returned will be saved. */ - function settings_form_submit($form_values) { + public function settings_form_submit($form_values) { return $form_values; } - /** - * Create a new destination of the correct type. + * Check that a destination is valid. */ - function create($params = array()) { - $out = NULL; - $types = backup_migrate_get_destination_types(); - - // Get the type passed in in the params, or if none, check the url for a valid type name. - // This is to allow new destination type to be specified in the path. - $destination_type = !empty($params['type']) ? $params['type'] : arg(BACKUP_MIGRATE_MENU_DEPTH + 3); - - if ($destination_type && ($type = @$types[$destination_type])) { - // Include the necessary file if specified by the type. - if (!empty($type['file'])) { - require_once './'. $type['file']; - } - $out = new $type['class']($params + array('destination_type' => $destination_type)); - } - if (empty($out)) { - $out = new backup_migrate_destination(); - } - return $out; + public function confirm_destination() { + return TRUE; } /** * Add the menu items specific to the destination type. */ - function get_menu_items() { + public function get_menu_items() { $items = parent::get_menu_items(); - $items[BACKUP_MIGRATE_MENU_PATH . '/destination/list/files'] = array( + + $path = $this->get_settings_path(); + + $items[$path . '/list/files'] = array( 'title' => 'Destination Files', 'page callback' => 'backup_migrate_menu_callback', - 'page arguments' => array('destinations', 'backup_migrate_ui_destination_display_files', TRUE), + 'page arguments' => array( + 'destinations', + 'backup_migrate_ui_destination_display_files', + TRUE, + ), 'access arguments' => array('access backup files'), 'type' => MENU_LOCAL_TASK, ); - $items[BACKUP_MIGRATE_MENU_PATH . '/destination/deletefile'] = array( + $items[$path . '/list/deletefile'] = array( 'title' => 'Delete File', 'description' => 'Delete a backup file', 'page callback' => 'backup_migrate_menu_callback', - 'page arguments' => array('destinations', 'backup_migrate_ui_destination_delete_file', TRUE), + 'page arguments' => array( + 'destinations', + 'backup_migrate_ui_destination_delete_file', + TRUE, + ), 'access arguments' => array('delete backup files'), - 'type' => MENU_CALLBACK, + 'type' => MENU_LOCAL_TASK, ); - $items[BACKUP_MIGRATE_MENU_PATH . '/destination/restorefile'] = array( + $items[$path . '/list/restorefile'] = array( 'title' => 'Restore from backup', 'description' => 'Restore database from a backup file on the server', 'page callback' => 'backup_migrate_menu_callback', - 'page arguments' => array('destinations', 'backup_migrate_ui_destination_restore_file', TRUE), + 'page arguments' => array( + 'destinations', + 'backup_migrate_ui_destination_restore_file', + TRUE, + ), 'access arguments' => array('restore from backup'), - 'type' => MENU_CALLBACK, + 'type' => MENU_LOCAL_TASK, ); - $items[BACKUP_MIGRATE_MENU_PATH . '/destination/downloadfile'] = array( + $items[$path . '/downloadfile'] = array( 'title' => 'Download File', 'description' => 'Download a backup file', 'page callback' => 'backup_migrate_menu_callback', - 'page arguments' => array('destinations', 'backup_migrate_ui_destination_download_file', TRUE), + 'page arguments' => array( + 'destinations', + 'backup_migrate_ui_destination_download_file', + TRUE, + ), 'access arguments' => array('access backup files'), 'type' => MENU_CALLBACK, ); return $items; } - /** - * Get the form for the settings for this filter. - */ - function backup_settings_default() { - return array(); - } - - /** - * Get the form for the settings for this filter. - */ - function backup_settings_form($settings) { - return array(); - } - - /** - * Get the form for the settings for this filter. - */ - function backup_settings_form_validate($form, &$form_state) { - } - - /** - * Submit the settings form. Any values returned will be saved. - */ - function backup_settings_form_submit($form, &$form_state) { - } - - /** - * Get the form for the settings for this filter. - */ - function restore_settings_default() { - return array(); - } - - /** - * Get the form for the settings for this filter. - */ - function restore_settings_form($settings) { - return array(); - } - - /** - * Get the form for the settings for this filter. - */ - function restore_settings_form_validate($form_values) { - } - - /** - * Submit the settings form. Any values returned will be saved. - */ - function restore_settings_form_submit($form_values) { - return $form_values; - } } /** * A base class for creating destinations. */ class backup_migrate_destination_remote extends backup_migrate_destination { + /** * The location is a URI so parse it and store the parts. */ - function get_location() { + public function get_location() { return $this->url(FALSE); } /** * The location to display is the url without the password. */ - function get_display_location() { + public function get_display_location() { return $this->url(TRUE); } /** * Return the location with the password. */ - function set_location($location) { + public function set_location($location) { $this->location = $location; $this->set_url($location); } - /** - * Get a url from the parts. - */ - function url($hide_password = TRUE) { - return $this->glue_url($this->dest_url, $hide_password); - } - - /** - * Glue a URLs component parts back into a URL. - */ - function glue_url($parts, $hide_password = TRUE) { - // Obscure the password if we need to. - $parts['pass'] = $hide_password ? "" : $parts['pass']; - - // Assemble the URL. - $out = ""; - $out .= $parts['scheme'] .'://'; - $out .= $parts['user'] ? urlencode($parts['user']) : ''; - $out .= ($parts['user'] && $parts['pass']) ? ":". urlencode($parts['pass']) : ''; - $out .= ($parts['user'] || $parts['pass']) ? "@" : ""; - $out .= $parts['host']; - $out .= !empty($parts['port']) ? ':'. $parts['port'] : ''; - $out .= "/". $parts['path']; - return $out; - } - - /** - * Break a URL into it's component parts. - */ - function set_url($url) { - $parts = (array)parse_url($url); - $parts['user'] = urldecode(@$parts['user']); - $parts['pass'] = urldecode(@$parts['pass']); - $parts['path'] = urldecode(@$parts['path']); - $parts['path'] = ltrim(@$parts['path'], "/"); - $this->dest_url = $parts; - } - /** * Destination configuration callback. */ - function edit_form() { + public function edit_form() { $form = parent::edit_form(); $form['scheme'] = array( - "#type" => "value", + "#type" => "textfield", "#title" => t("Scheme"), - "#default_value" => @$this->dest_url['scheme'] ? $this->dest_url['scheme'] : 'mysql', + "#default_value" => @$this->dest_url['scheme'] ? $this->dest_url['scheme'] : '', "#required" => TRUE, -// "#options" => array($GLOBALS['db_type'] => $GLOBALS['db_type']), "#weight" => 0, ); $form['host'] = array( @@ -1132,18 +1326,21 @@ class backup_migrate_destination_remote extends backup_migrate_destination { "#type" => "value", "#value" => @$this->dest_url['pass'], ); - $form['pass']["#description"] .= t(' You do not need to enter a password unless you wish to change the currently saved password.'); + $form['pass']["#description"] .= ' ' . t('You do not need to enter a password unless you wish to change the currently saved password.'); } return $form; } /** - * Submit the configuration form. Glue the url together and add the old password back if a new one was not specified. + * Submit the configuration form. + * + * Glue the url together and add the old password back if a new one was not + * specified. */ - function edit_form_submit($form, &$form_state) { + public function edit_form_submit($form, &$form_state) { $form_state['values']['pass'] = $form_state['values']['pass'] ? $form_state['values']['pass'] : $form_state['values']['old_password']; $form_state['values']['location'] = $this->glue_url($form_state['values'], FALSE); parent::edit_form_submit($form, $form_state); } -} +} diff --git a/sites/all/modules/contrib/admin/backup_migrate/includes/destinations.s3.inc b/sites/all/modules/contrib/admin/backup_migrate/includes/destinations.s3.inc index 8937162..fbf8db7 100644 --- a/sites/all/modules/contrib/admin/backup_migrate/includes/destinations.s3.inc +++ b/sites/all/modules/contrib/admin/backup_migrate/includes/destinations.s3.inc @@ -1,6 +1,5 @@ s3_object()) { $path = $file->filename(); if ($s3->putObject($s3->inputFile($file->filepath(), FALSE), $this->get_bucket(), $this->remote_path($file->filename()), S3::ACL_PRIVATE)) { @@ -33,8 +31,9 @@ class backup_migrate_destination_s3 extends backup_migrate_destination_remote { /** * Load from the s3 destination. */ - function load_file($file_id) { - backup_migrate_include('files'); + public function load_file($file_id) { + require_once dirname(__FILE__) . '/files.inc'; + $file = new backup_file(array('filename' => $file_id)); if ($s3 = $this->s3_object()) { $data = $s3->getObject($this->get_bucket(), $this->remote_path($file_id), $file->filepath()); @@ -42,13 +41,14 @@ class backup_migrate_destination_s3 extends backup_migrate_destination_remote { return $file; } } + return NULL; } /** * Delete from the s3 destination. */ - function _delete_file($file_id) { + public function _delete_file($file_id) { if ($s3 = $this->s3_object()) { $s3->deleteObject($this->get_bucket(), $this->remote_path($file_id)); } @@ -57,12 +57,13 @@ class backup_migrate_destination_s3 extends backup_migrate_destination_remote { /** * List all files from the s3 destination. */ - function _list_files() { - backup_migrate_include('files'); + public function _list_files() { + require_once dirname(__FILE__) . '/files.inc'; + $files = array(); if ($s3 = $this->s3_object()) { $s3_files = $s3->getBucket($this->get_bucket(), $this->get_subdir()); - foreach ((array)$s3_files as $id => $file) { + foreach ((array) $s3_files as $id => $file) { $info = array( 'filename' => $this->local_path($file['name']), 'filesize' => $file['size'], @@ -71,21 +72,21 @@ class backup_migrate_destination_s3 extends backup_migrate_destination_remote { $files[$info['filename']] = new backup_file($info); } } + return $files; } /** * Get the form for the settings for this filter. */ - function edit_form() { + public function edit_form() { // Check for the library. $this->s3_object(); $form = parent::edit_form(); $form['scheme']['#type'] = 'value'; $form['scheme']['#value'] = 'https'; - $form['host']['#type'] = 'value'; - $form['host']['#value'] = 's3.amazonaws.com'; + $form['host']['#default_value'] = @$this->dest_url['host'] ? $this->dest_url['host'] : 's3.amazonaws.com'; $form['path']['#title'] = 'S3 Bucket'; $form['path']['#default_value'] = $this->get_bucket(); @@ -98,7 +99,7 @@ class backup_migrate_destination_s3 extends backup_migrate_destination_remote { '#type' => 'textfield', '#title' => t('Subdirectory'), '#default_value' => $this->get_subdir(), - '#weight' => 25 + '#weight' => 25, ); $form['settings']['#weight'] = 50; @@ -108,11 +109,10 @@ class backup_migrate_destination_s3 extends backup_migrate_destination_remote { /** * Submit the form for the settings for the s3 destination. */ - function edit_form_submit($form, &$form_state) { + public function edit_form_submit($form, &$form_state) { // Append the subdir onto the path. - if (!empty($form_state['values']['subdir'])) { - $form_state['values']['path'] .= '/'. trim($form_state['values']['subdir'], '/'); + $form_state['values']['path'] .= '/' . trim($form_state['values']['subdir'], '/'); } parent::edit_form_submit($form, $form_state); } @@ -120,9 +120,9 @@ class backup_migrate_destination_s3 extends backup_migrate_destination_remote { /** * Generate a filepath with the correct prefix. */ - function remote_path($path) { + public function remote_path($path) { if ($subdir = $this->get_subdir()) { - $path = $subdir .'/'. $path; + $path = $subdir . '/' . $path; } return $path; } @@ -130,9 +130,9 @@ class backup_migrate_destination_s3 extends backup_migrate_destination_remote { /** * Generate a filepath with the correct prefix. */ - function local_path($path) { + public function local_path($path) { if ($subdir = $this->get_subdir()) { - $path = str_replace($subdir .'/', '', $path); + $path = str_replace($subdir . '/', '', $path); } return $path; } @@ -140,7 +140,7 @@ class backup_migrate_destination_s3 extends backup_migrate_destination_remote { /** * Get the bucket which is the first part of the path. */ - function get_bucket() { + public function get_bucket() { $parts = explode('/', @$this->dest_url['path']); return $parts[0]; } @@ -148,7 +148,7 @@ class backup_migrate_destination_s3 extends backup_migrate_destination_remote { /** * Get the bucket which is the first part of the path. */ - function get_subdir() { + public function get_subdir() { // Support the older style of subdir saving. if ($subdir = $this->settings('subdir')) { return $subdir; @@ -158,7 +158,10 @@ class backup_migrate_destination_s3 extends backup_migrate_destination_remote { return implode('/', array_filter($parts)); } - function s3_object() { + /** + * + */ + public function s3_object() { // Try to use libraries module if available to find the path. if (function_exists('libraries_get_path')) { $library_paths[] = libraries_get_path('s3-php5-curl'); @@ -169,11 +172,16 @@ class backup_migrate_destination_s3 extends backup_migrate_destination_remote { $library_paths[] = drupal_get_path('module', 'backup_migrate') . '/includes/s3-php5-curl'; $library_paths[] = drupal_get_path('module', 'backup_migrate') . '/includes'; - foreach($library_paths as $path) { + foreach ($library_paths as $path) { if (file_exists($path . '/S3.php')) { require_once $path . '/S3.php'; if (!$this->s3 && !empty($this->dest_url['user'])) { - $this->s3 = new S3($this->dest_url['user'], $this->dest_url['pass']); + // The hostname can be overridden. + $host = 's3.amazonaws.com'; + if (isset($this->dest_url['host'])) { + $host = $this->dest_url['host']; + } + $this->s3 = new S3($this->dest_url['user'], $this->dest_url['pass'], FALSE, $host); } return $this->s3; } @@ -181,4 +189,5 @@ class backup_migrate_destination_s3 extends backup_migrate_destination_remote { drupal_set_message(t('Due to drupal.org code hosting policies, the S3 library needed to use an S3 destination is no longer distributed with this module. You must download the library from !link and place it in one of these locations: %locations.', array('%locations' => implode(', ', $library_paths), '!link' => l('http://undesigned.org.za/2007/10/22/amazon-s3-php-class', 'http://undesigned.org.za/2007/10/22/amazon-s3-php-class'))), 'error', FALSE); return NULL; } + } diff --git a/sites/all/modules/contrib/admin/backup_migrate/includes/files.inc b/sites/all/modules/contrib/admin/backup_migrate/includes/files.inc index d7a61c2..b511814 100644 --- a/sites/all/modules/contrib/admin/backup_migrate/includes/files.inc +++ b/sites/all/modules/contrib/admin/backup_migrate/includes/files.inc @@ -1,6 +1,5 @@ getDirectoryPath(); - $expire = time() - variable_get('backup_migrate_cleanup_time', 21600); - if (file_exists($dir) && is_dir($dir) && is_readable($dir) && $handle = opendir($dir)) { - while (FALSE !== ($file = @readdir($handle))) { - // Delete 'backup_migrate_' files in the temp directory that are older than the expire time. - // We should only attempt to delete writable files to prevent errors in shared environments. - // This could still cause issues in shared environments with poorly configured file permissions. - if (strpos($file, 'backup_migrate_') === 0 && is_writable("$dir/$file") && @filectime("$dir/$file") < $expire) { - unlink("$dir/$file"); +/** + * Move files recursively. + */ +function _backup_migrate_move_files($from, $to) { + if (is_readable($from)) { + if (is_dir($from) && is_readable($from) && $handle = opendir($from)) { + if (!file_exists($to)) { + mkdir($to); } + while (FALSE !== ($file = @readdir($handle))) { + if ($file != '..' && $file != '.') { + _backup_migrate_move_files("$from/$file", "$to/$file"); + } + } + closedir($handle); + } + else { + rename($from, $to); } - closedir($handle); } + return FALSE; +} + +/** + * Create a temporary directory. + */ +function backup_migrate_temp_directory() { + $tmp = realpath(file_directory_temp()); + // Check the writability of the temp directory. + if (!is_writable(realpath(file_directory_temp()))) { + _backup_migrate_message('Your temporary directory %tmp is not writable. Backup and migrate needs to be able to create temporary files.', array('%tmp' => $tmp), 'error'); + } + + // Use a full path so that the files can be deleted during the shutdown function if needed. + $file = $tmp . '/' . uniqid('backup_migrate_'); + mkdir($file); + backup_migrate_temp_files_add($file); + return $file; } /** * Return a list of backup filetypes. */ function _backup_migrate_filetypes() { - backup_migrate_include('filters'); + require_once dirname(__FILE__) . '/filters.inc'; $out = backup_migrate_filters_file_types(); foreach ($out as $key => $info) { $out[$key]['id'] = empty($info['id']) ? $key : $info['id']; } + return $out; } @@ -78,19 +140,33 @@ function _backup_migrate_filename_append_prepare($filename, $append_str) { /** * Construct a filename using token and some cleaning. */ -function _backup_migrate_construct_filename($filename, $timestamp='') { - if (module_exists('token')) { +function _backup_migrate_construct_filename($settings) { + // Get the raw filename from the settings. + $filename = $settings->filename; + + // Replace any tokens. + if (module_exists('token') && function_exists('token_replace')) { $filename = token_replace($filename); } - $filename = preg_replace("/[^a-zA-Z0-9\.\-_]/", "", $filename); + + // Remove illegal characters. + $filename = _backup_migrate_clean_filename($filename); + + // Generate a timestamp if needed. + $timestamp = ''; + if ($settings->append_timestamp == 1 && $settings->timestamp_format) { + $timestamp = format_date(time(), 'custom', $settings->timestamp_format); + } + + // Trim to length if needed to allow the timestamp to fit into the max filename. $filename = _backup_migrate_filename_append_prepare($filename, $timestamp); $filename .= '-' . $timestamp; $filename = trim($filename, '-'); + // If there is no filename, then just call it untitled. if (drupal_strlen($filename) == 0) { $filename = 'untitled'; } - return $filename; } @@ -102,11 +178,20 @@ function _backup_migrate_default_filename() { return '[site:name]'; } else { - // Cleaning the string isn't strictly necessary but it looks better in the settings field. - return variable_get('site_name', 'backup_migrate'); + // Cleaning the string isn't strictly necessary but it looks better in the + // settings field. + return _backup_migrate_clean_filename(variable_get('site_name', "backup_migrate")); } } +/** + * Clean up a filename to remove unsafe characters. + */ +function _backup_migrate_clean_filename($filename) { + $filename = preg_replace("/[^a-zA-Z0-9\.\-_]/", "", $filename); + return $filename; +} + /** * An output buffer callback which simply throws away the buffer instead of sending it to the browser. */ @@ -114,22 +199,21 @@ function _backup_migrate_file_dispose_buffer($buffer) { return ""; } - /** * A backup file which allows for saving to and reading from the server. */ class backup_file { - var $file_info = array(); - var $type = array(); - var $ext = array(); - var $path = ""; - var $name = ""; - var $handle = NULL; + public $file_info = array(); + public $type = array(); + public $ext = array(); + public $path = ""; + public $name = ""; + public $handle = NULL; /** * Construct a file object given a file path, or create a temp file for writing. */ - function backup_file($params = array()) { + public function __construct($params = array()) { if (isset($params['filepath']) && file_exists($params['filepath'])) { $this->set_filepath($params['filepath']); } @@ -142,7 +226,7 @@ class backup_file { /** * Get the file_id if the file has been saved to a destination. */ - function file_id() { + public function file_id() { // The default file_id is the filename. Destinations can override the file_id if needed. return isset($this->file_info['file_id']) ? $this->file_info['file_id'] : $this->filename(); } @@ -150,27 +234,33 @@ class backup_file { /** * Get the current filepath. */ - function filepath() { - return drupal_realpath($this->path); + public function filepath() { + if ($filepath = drupal_realpath($this->path)) { + return $filepath; + } + return $this->path; } /** * Get the final filename. */ - function filename($name = NULL) { + public function filename($name = NULL) { if ($name) { $this->name = $name; } - return $this->name .'.'. $this->extension(); + $extension_str = '.' . $this->extension(); + $this->name = _backup_migrate_filename_append_prepare($this->name, $extension_str); + return $this->name . $extension_str; } /** * Set the current filepath. */ - function set_filepath($path) { + public function set_filepath($path) { $this->path = $path; $params = array( 'filename' => basename($path), + 'file_id' => basename($path), ); if (file_exists($path)) { $params['filesize'] = filesize($path); @@ -182,67 +272,93 @@ class backup_file { /** * Get one or all pieces of info for the file. */ - function info($key = NULL) { + public function info($key = NULL) { if ($key) { return @$this->file_info[$key]; } return $this->file_info; } + /** + * Get one or all pieces of info for the file. + */ + public function info_set($key, $value) { + $this->file_info[$key] = $value; + } + /** * Get the file extension. */ - function extension() { + public function extension() { return implode(".", $this->ext); } /** * Get the file type. */ - function type() { + public function type() { return $this->type; } /** * Get the file mimetype. */ - function mimetype() { + public function mimetype() { return @$this->type['filemime'] ? $this->type['filemime'] : 'application/octet-stream'; } /** * Get the file mimetype. */ - function type_id() { + public function type_id() { return @$this->type['id']; } + /** + * + */ + public function filesize() { + if (empty($this->file_info['filesize'])) { + $this->calculate_filesize(); + } + return $this->file_info['filesize']; + } + + /** + * + */ + public function calculate_filesize() { + $this->file_info['filesize'] = ''; + if (!empty($this->path) && file_exists($this->path)) { + $this->file_info['filesize'] = filesize($this->path); + } + } /** * Can this file be used to backup to. */ - function can_backup() { + public function can_backup() { return @$this->type['backup']; } /** * Can this file be used to restore to. */ - function can_restore() { + public function can_restore() { return @$this->type['restore']; } /** * Can this file be used to restore to. */ - function is_recognized_type() { + public function is_recognized_type() { return @$this->type['restore'] || @$this->type['backup']; } /** * Open a file for reading or writing. */ - function open($write = FALSE, $binary = FALSE) { + public function open($write = FALSE, $binary = FALSE) { if (!$this->handle) { $path = $this->filepath(); @@ -267,7 +383,7 @@ class backup_file { /** * Close a file when we're done reading/writing. */ - function close() { + public function close() { fclose($this->handle); $this->handle = NULL; } @@ -275,7 +391,7 @@ class backup_file { /** * Write a line to the file. */ - function write($data) { + public function write($data) { if (!$this->handle) { $this->handle = $this->open(TRUE); } @@ -287,7 +403,7 @@ class backup_file { /** * Read a line from the file. */ - function read($size = NULL) { + public function read($size = NULL) { if (!$this->handle) { $this->handle = $this->open(); } @@ -300,14 +416,14 @@ class backup_file { /** * Write data to the file. */ - function put_contents($data) { + public function put_contents($data) { file_put_contents($this->filepath(), $data); } /** * Read data from the file. */ - function get_contents() { + public function get_contents() { return file_get_contents($this->filepath()); } @@ -315,11 +431,16 @@ class backup_file { * Transfer file using http to client. Similar to the built in file_transfer, * but it calls module_invoke_all('exit') so that temp files can be deleted. */ - function transfer() { + public function transfer() { $headers = array( array('key' => 'Content-Type', 'value' => $this->mimetype()), - array('key' => 'Content-Disposition', 'value' => 'attachment; filename="'. $this->filename() .'"'), + array('key' => 'Content-Disposition', 'value' => 'attachment; filename="' . $this->filename() . '"'), ); + // In some circumstances, web-servers will double compress gzipped files. + // This may help aleviate that issue by disabling mod-deflate. + if ($this->mimetype() == 'application/x-gzip') { + $headers[] = array('key' => 'Content-Encoding', 'value' => 'gzip'); + } if ($size = $this->info('filesize')) { $headers[] = array('key' => 'Content-Length', 'value' => $size); } @@ -358,7 +479,7 @@ class backup_file { /** * Push a file extension onto the file and return the previous file path. */ - function push_type($extension) { + public function push_type($extension) { $types = _backup_migrate_filetypes(); if ($type = @$types[$extension]) { $this->push_filetype($type); @@ -372,7 +493,7 @@ class backup_file { /** * Push a file extension onto the file and return the previous file path. */ - function pop_type() { + public function pop_type() { $out = new backup_file(array('filepath' => $this->filepath())); $this->pop_filetype(); $this->temporary_file(); @@ -382,7 +503,7 @@ class backup_file { /** * Set the current file type. */ - function set_filetype($type) { + public function set_filetype($type) { $this->type = $type; $this->ext = array($type['extension']); } @@ -390,7 +511,7 @@ class backup_file { /** * Set the current file type. */ - function push_filetype($type) { + public function push_filetype($type) { $this->ext[] = $type['extension']; $this->type = $type; } @@ -398,7 +519,7 @@ class backup_file { /** * Pop the current file type. */ - function pop_filetype() { + public function pop_filetype() { array_pop($this->ext); $this->detect_filetype_from_extension(); } @@ -406,7 +527,7 @@ class backup_file { /** * Set the file info. */ - function set_file_info($file_info) { + public function set_file_info($file_info) { $this->file_info = $file_info; $this->ext = explode('.', @$this->file_info['filename']); @@ -421,7 +542,7 @@ class backup_file { /** * Get the filetype info of the given file, or false if the file is not a valid type. */ - function detect_filetype_from_extension() { + public function detect_filetype_from_extension() { $ext = end($this->ext); $this->type = array(); $types = _backup_migrate_filetypes(); @@ -436,17 +557,17 @@ class backup_file { /** * Get a temporary file name with path. */ - function temporary_file() { + public function temporary_file() { $file = drupal_tempnam('temporary://', 'backup_migrate_'); // Add the version without the extension. The tempnam function creates this for us. backup_migrate_temp_files_add($file); if ($this->extension()) { - $file .= '.'. $this->extension(); + $file .= '.' . $this->extension(); // Add the version with the extension. This is the one we will actually use. backup_migrate_temp_files_add($file); } $this->path = $file; } -} +} diff --git a/sites/all/modules/contrib/admin/backup_migrate/includes/filters.backup_restore.inc b/sites/all/modules/contrib/admin/backup_migrate/includes/filters.backup_restore.inc index e9f2519..12578a5 100644 --- a/sites/all/modules/contrib/admin/backup_migrate/includes/filters.backup_restore.inc +++ b/sites/all/modules/contrib/admin/backup_migrate/includes/filters.backup_restore.inc @@ -1,9 +1,10 @@ 0, 'restore' => 0); + public $op_weights = array('backup' => 0, 'restore' => 0); /** * Get the default destinations for this filter. */ - function destinations() { + public function destinations() { $out = array(); foreach ($this->_get_destination_types() as $destination) { if (method_exists($destination, 'destinations')) { @@ -27,15 +28,28 @@ class backup_migrate_filter_backup_restore extends backup_migrate_filter { return $out; } + /** + * Get the default sources for this filter. + */ + public function sources() { + $out = array(); + foreach ($this->_get_source_types() as $type) { + if (method_exists($type, 'sources')) { + $out += $type->sources(); + } + } + return $out; + } /** * Get the default backup settings for this filter. */ - function backup_settings_default() { - backup_migrate_include('destinations'); + public function backup_settings_default() { + require_once dirname(__FILE__) . '/sources.inc'; + $out = array(); - foreach (backup_migrate_get_destinations('source') as $destination) { - $out['destinations'][$destination->get_id()] = $destination->backup_settings_default(); + foreach (backup_migrate_get_sources() as $source) { + $out['sources'][$source->get_id()] = $source->backup_settings_default(); } return $out; } @@ -43,7 +57,7 @@ class backup_migrate_filter_backup_restore extends backup_migrate_filter { /** * Get the form for the settings for this filter. */ - function backup_settings_form_validate($form, &$form_state) { + public function backup_settings_form_validate($form, &$form_state) { foreach ($this->_get_destination_types() as $destination) { $destination->backup_settings_form_validate($form, $form_state); } @@ -52,7 +66,7 @@ class backup_migrate_filter_backup_restore extends backup_migrate_filter { /** * Submit the settings form. Any values returned will be saved. */ - function backup_settings_form_submit($form, &$form_state) { + public function backup_settings_form_submit($form, &$form_state) { foreach ($this->_get_destination_types() as $destination) { $destination->backup_settings_form_submit($form, $form_state); } @@ -61,7 +75,7 @@ class backup_migrate_filter_backup_restore extends backup_migrate_filter { /** * Get the default restore settings for this filter. */ - function restore_settings_default() { + public function restore_settings_default() { $out = array(); foreach ($this->_get_destination_types() as $destination) { $out += $destination->restore_settings_default(); @@ -72,22 +86,24 @@ class backup_migrate_filter_backup_restore extends backup_migrate_filter { /** * Get the form for the backup settings for this filter. */ - function backup_settings_form($settings) { - backup_migrate_include('destinations'); - $out = array('destinations' => array( - '#tree' => TRUE, - - )); - foreach (backup_migrate_get_destinations('source') as $destination) { - $destination_settings = (array)(@$settings['destinations'][$destination->get_id()]) + $settings; - if ($form = $destination->backup_settings_form($destination_settings)) { - $out['destinations'][$destination->get_id()] = array( + public function backup_settings_form($settings) { + require_once dirname(__FILE__) . '/sources.inc'; + + $out = array( + 'sources' => array( + '#tree' => TRUE, + ), + ); + foreach (backup_migrate_get_sources() as $source) { + $source_settings = (array) (@$settings['sources'][$source->get_id()]) + $settings; + if ($form = $source->backup_settings_form($source_settings)) { + $out['sources'][$source->get_id()] = array( '#type' => 'fieldset', - '#title' => t('!name Backup Options', array('!name' => $destination->get('name'))), + '#title' => t('!name Backup Options', array('!name' => $source->get('name'))), "#collapsible" => TRUE, "#collapsed" => TRUE, '#tree' => TRUE, - '#parents' => array('filters', 'destinations', $destination->get_id()), + '#parents' => array('filters', 'sources', $source->get_id()), ) + $form; } } @@ -97,7 +113,7 @@ class backup_migrate_filter_backup_restore extends backup_migrate_filter { /** * Get the form for the restore settings for this filter. */ - function restore_settings_form($settings) { + public function restore_settings_form($settings) { $form = array(); foreach ($this->_get_destination_types() as $destination) { $destination->restore_settings_form($form, $settings); @@ -105,25 +121,72 @@ class backup_migrate_filter_backup_restore extends backup_migrate_filter { return $form; } + /** + * Get the before-backup form for the active sources and destinations. + */ + public function before_action_form($op, $settings) { + $form = array(); + $method = 'before_' . $op . '_form'; + if ($source = $settings->get_source()) { + if (method_exists($source, $method)) { + $form += $source->{$method}($settings); + } + } + foreach ($settings->get_destinations() as $destination) { + if (method_exists($destination, $method)) { + $form += $destination->{$method}($settings); + } + } + return $form; + } + + /** + * Get the before-backup form for the active sources and destinations. + */ + public function before_action_form_validate($op, $settings, $form, $form_state) { + $method = 'before_' . $op . '_form_validate'; + foreach ($settings->get_all_locations() as $location) { + if (method_exists($location, $method)) { + $location->{$method}($settings, $form, $form_state); + } + } + } + + /** + * Get the before-backup form for the active sources and destinations. + */ + public function before_action_form_submit($op, $settings, $form, $form_state) { + $method = 'before_' . $op . '_form_submit'; + foreach ($settings->get_all_locations() as $location) { + if (method_exists($location, $method)) { + $location->{$method}($settings, $form, $form_state); + } + } + } + /** * Get the file types supported by this destination. */ - function file_types() { + public function file_types() { $types = array(); foreach ($this->_get_destination_types() as $destination) { $types += $destination->file_types(); } + foreach ($this->_get_source_types() as $source) { + $types += $source->file_types(); + } return $types; } /** * Backup the data from the source specified in the settings. */ - function backup($file, &$settings) { + public function backup($file, $settings) { if ($source = $settings->get_source()) { - if (!empty($settings->filters['destinations'][$source->get_id()])) { - $settings->filters = (array)($settings->filters['destinations'][$source->get_id()]) + $settings->filters; + if (!empty($settings->filters['sources'][$source->get_id()])) { + $settings->filters = (array) ($settings->filters['sources'][$source->get_id()]) + $settings->filters; } + $file = $source->backup_to_file($file, $settings); return $file; } @@ -134,10 +197,10 @@ class backup_migrate_filter_backup_restore extends backup_migrate_filter { /** * Restore the data from to source specified in the settings. */ - function restore($file, &$settings) { + public function restore($file, $settings) { if ($source = $settings->get_source()) { - if (!empty($settings->filters['destinations'][$source->get_id()])) { - $settings->filters = (array)($settings->filters['destinations'][$source->get_id()]) + $settings->filters; + if (!empty($settings->filters['sources'][$source->get_id()])) { + $settings->filters = (array) ($settings->filters['sources'][$source->get_id()]) + $settings->filters; } $num = $source->restore_from_file($file, $settings); return $num ? $file : FALSE; @@ -147,23 +210,54 @@ class backup_migrate_filter_backup_restore extends backup_migrate_filter { } /** - * Get a list of dummy destinations representing each of the available destination types. + * Dummy destinations representing each of the available destination types. + * + * @return array + * All of the available destination types. */ - function _get_destination_types() { - backup_migrate_include('destinations'); - static $destinations = NULL; + public function _get_destination_types() { + require_once dirname(__FILE__) . '/destinations.inc'; + + $destinations = &drupal_static('backup_migrate_filter_backup_restore::_get_destination_types', NULL); if (!is_array($destinations)) { $destinations = array(); - $types = backup_migrate_get_destination_types(); + $types = backup_migrate_get_destination_subtypes(); // If no (valid) node type has been provided, display a node type overview. foreach ($types as $key => $type) { // Include the necessary file if specified by the type. if (!empty($type['file'])) { - require_once './'. $type['file']; + require_once './' . $type['file']; } $destinations[] = new $type['class'](array()); } } return $destinations; } + + /** + * Dummy destinations representing each of the available source types. + * + * @return array + * All of the available source types. + */ + public function _get_source_types() { + require_once dirname(__FILE__) . '/sources.inc'; + + $sources = &drupal_static('backup_migrate_filter_backup_restore::_get_source_types', NULL); + if (!is_array($sources)) { + $sources = array(); + $types = backup_migrate_get_source_subtypes(); + // If no (valid) node type has been provided, display a node type + // overview. + foreach ($types as $key => $type) { + // Include the necessary file if specified by the type. + if (!empty($type['file'])) { + require_once './' . $type['file']; + } + $sources[] = new $type['class'](array()); + } + } + return $sources; + } + } diff --git a/sites/all/modules/contrib/admin/backup_migrate/includes/filters.compression.inc b/sites/all/modules/contrib/admin/backup_migrate/includes/filters.compression.inc index 6a0e111..e55ff92 100644 --- a/sites/all/modules/contrib/admin/backup_migrate/includes/filters.compression.inc +++ b/sites/all/modules/contrib/admin/backup_migrate/includes/filters.compression.inc @@ -1,6 +1,5 @@ 100, 'restore' => -100); + public $op_weights = array('backup' => 100, 'restore' => -100); /** * This function is called on a backup file after the backup has been completed. */ - function backup($file, &$settings) { + public function backup($file, $settings) { return $this->_backup_migrate_file_compress($file, $settings); } /** * This function is called on a backup file before importing it. */ - function restore($file, &$settings) { - return $this->_backup_migrate_file_decompress($file); + public function restore($file, $settings) { + return $this->_backup_migrate_file_decompress($file, $settings); } /** * Get the form for the settings for this filter. */ - function backup_settings_default() { + public function backup_settings_default() { $options = $this->_backup_migrate_get_compression_form_item_options(); return array('compression' => isset($options['gzip']) ? 'gzip' : 'none'); } @@ -39,7 +38,7 @@ class backup_migrate_filter_compression extends backup_migrate_filter { /** * Get the form for the settings for this filter. */ - function backup_settings_form($settings) { + public function backup_settings_form($settings) { $form = array(); $compression_options = $this->_backup_migrate_get_compression_form_item_options(); $form['file']['compression'] = array( @@ -54,7 +53,7 @@ class backup_migrate_filter_compression extends backup_migrate_filter { /** * Return a list of backup filetypes. */ - function file_types() { + public function file_types() { return array( "gzip" => array( "extension" => "gz", @@ -86,7 +85,7 @@ class backup_migrate_filter_compression extends backup_migrate_filter { /** * Get the compression options as an options array for a form item. */ - function _backup_migrate_get_compression_form_item_options() { + public function _backup_migrate_get_compression_form_item_options() { $compression_options = array("none" => t("No Compression")); if (@function_exists("gzencode")) { $compression_options['gzip'] = t("GZip"); @@ -103,10 +102,14 @@ class backup_migrate_filter_compression extends backup_migrate_filter { /** * Gzip encode a file. */ - function _backup_migrate_gzip_encode($source, $dest, $level = 9) { + public function _backup_migrate_gzip_encode($source, $dest, $level = 9, $settings) { $success = FALSE; - if (@function_exists("gzopen")) { - if (($fp_out = gzopen($dest, 'wb'. $level)) && ($fp_in = fopen($source, 'rb'))) { + // Try command line gzip first. + if (!empty($settings->filters['use_cli'])) { + $success = backup_migrate_exec("gzip -c -$level %input > %dest", array('%input' => $source, '%dest' => $dest, '%level' => $level)); + } + if (!$success && @function_exists("gzopen")) { + if (($fp_out = gzopen($dest, 'wb' . $level)) && ($fp_in = fopen($source, 'rb'))) { while (!feof($fp_in)) { gzwrite($fp_out, fread($fp_in, 1024 * 512)); } @@ -121,9 +124,14 @@ class backup_migrate_filter_compression extends backup_migrate_filter { /** * Gzip decode a file. */ - function _backup_migrate_gzip_decode($source, $dest) { + public function _backup_migrate_gzip_decode($source, $dest, $settings) { $success = FALSE; - if (@function_exists("gzopen")) { + + if (!empty($settings->filters['use_cli'])) { + $success = backup_migrate_exec("gzip -d -c %input > %dest", array('%input' => $source, '%dest' => $dest)); + } + + if (!$success && @function_exists("gzopen")) { if (($fp_out = fopen($dest, 'wb')) && ($fp_in = gzopen($source, 'rb'))) { while (!feof($fp_in)) { fwrite($fp_out, gzread($fp_in, 1024 * 512)); @@ -139,7 +147,7 @@ class backup_migrate_filter_compression extends backup_migrate_filter { /** * Bzip encode a file. */ - function _backup_migrate_bzip_encode($source, $dest) { + public function _backup_migrate_bzip_encode($source, $dest) { $success = FALSE; if (@function_exists("bzopen")) { if (($fp_out = bzopen($dest, 'w')) && ($fp_in = fopen($source, 'rb'))) { @@ -160,7 +168,7 @@ class backup_migrate_filter_compression extends backup_migrate_filter { /** * Bzip decode a file. */ - function _backup_migrate_bzip_decode($source, $dest) { + public function _backup_migrate_bzip_decode($source, $dest) { $success = FALSE; if (@function_exists("bzopen")) { if (($fp_out = fopen($dest, 'w')) && ($fp_in = bzopen($source, 'r'))) { @@ -181,10 +189,10 @@ class backup_migrate_filter_compression extends backup_migrate_filter { /** * Zip encode a file. */ - function _backup_migrate_zip_encode($source, $dest, $filename) { + public function _backup_migrate_zip_encode($source, $dest, $filename) { $success = FALSE; if (class_exists('ZipArchive')) { - $zip = new ZipArchive; + $zip = new ZipArchive(); $res = $zip->open($dest, constant("ZipArchive::CREATE")); if ($res === TRUE) { $zip->addFile($source, $filename); @@ -197,10 +205,10 @@ class backup_migrate_filter_compression extends backup_migrate_filter { /** * Zip decode a file. */ - function _backup_migrate_zip_decode($source, $dest) { + public function _backup_migrate_zip_decode($source, $dest) { $success = FALSE; if (class_exists('ZipArchive')) { - $zip = new ZipArchive; + $zip = new ZipArchive(); if (($fp_out = fopen($dest, "w")) && ($zip->open($source))) { $filename = ($zip->getNameIndex(0)); if ($fp_in = $zip->getStream($filename)) { @@ -220,11 +228,11 @@ class backup_migrate_filter_compression extends backup_migrate_filter { * Compress a file with the given settings. * Also updates settings to reflect new file mime and file extension. */ - function _backup_migrate_file_compress($file, $settings) { + public function _backup_migrate_file_compress($file, $settings) { switch ($settings->filters['compression']) { case "gzip": $from = $file->push_type('gzip'); - if (!$success = $this->_backup_migrate_gzip_encode($from, $file->filepath(), 9)) { + if (!$success = $this->_backup_migrate_gzip_encode($from, $file->filepath(), 9, $settings)) { $file = NULL; } break; @@ -247,7 +255,6 @@ class backup_migrate_filter_compression extends backup_migrate_filter { if (!$file) { _backup_migrate_message("Could not compress backup file. Try backing up without compression.", array(), 'error'); } - return $file; } @@ -255,13 +262,13 @@ class backup_migrate_filter_compression extends backup_migrate_filter { * Decompress a file with the given settings. * Also updates settings to reflect new file mime and file extension. */ - function _backup_migrate_file_decompress($file) { + public function _backup_migrate_file_decompress($file, $settings) { $success = FALSE; switch ($file->type_id()) { case "gzip": $from = $file->pop_type(); - $success = $this->_backup_migrate_gzip_decode($from->filepath(), $file->filepath()); + $success = $this->_backup_migrate_gzip_decode($from->filepath(), $file->filepath(), $settings); break; case "bzip": @@ -285,5 +292,5 @@ class backup_migrate_filter_compression extends backup_migrate_filter { } return $success ? $file : NULL; } -} +} diff --git a/sites/all/modules/contrib/admin/backup_migrate/includes/filters.encryption.inc b/sites/all/modules/contrib/admin/backup_migrate/includes/filters.encryption.inc index 46f01c2..61b0dec 100644 --- a/sites/all/modules/contrib/admin/backup_migrate/includes/filters.encryption.inc +++ b/sites/all/modules/contrib/admin/backup_migrate/includes/filters.encryption.inc @@ -1,6 +1,5 @@ 170, 'restore' => -170); + public $op_weights = array('backup' => 170, 'restore' => -170); /** - * This function is called on a backup file after the backup has been completed. + * Called on a backup file after the backup has been completed. */ - function backup($file, &$settings) { + public function backup($file, $settings) { return $this->file_encrypt($file, $settings); } /** - * This function is called on a backup file before importing it. + * Called on a backup file before importing it. */ - function restore($file, &$settings) { + public function restore($file, $settings) { return $this->file_decrypt($file); } /** - * Get the form for the settings for this filter. + * Gets the form for the settings for this filter. */ - function backup_settings_default() { + public function backup_settings_default() { return array('encryption' => 'none'); } /** - * Get the form for the settings for this filter. + * Gets the form for the settings for this filter. */ - function backup_settings_form($settings) { + public function backup_settings_form($settings) { $form = array(); $options = $this->_backup_migrate_get_encryption_form_item_options(); - if (count($options) > 1) { + + if (array_intersect_key($options, array_flip(array('aes', 'encrypt')))) { $form['file']['encryption'] = array( "#type" => "select", - "#title" => t("File Encryption (EXPERIMENTAL)"), + "#title" => t("File Encryption"), "#options" => $options, "#default_value" => @$settings['encryption'], - '#description' => t('Encrypted files can only be restored by Backup and Migrate and only on sites with the same encryption key. This functionality is experimental, and should only be used for testing.'), + '#description' => t('Encrypted files can only be restored by Backup and Migrate and only on sites with the same encryption key.'), ); } else { $form['file']['encryption'] = array( "#type" => 'item', "#title" => t("File Encryption"), - "#markup" => t('Install the !link to enable backup file encryption.', array('!link' => l(t('AES Encryption Module'), 'http://drupal.org/project/aes'))), + "#markup" => t('Install the !link to enable backup file encryption.', array('!link' => l(t('Encrypt module'), 'http://drupal.org/project/encrypt'))), ); } + // If the Encrypt method is available add a configuration field. + if (array_key_exists('encrypt', $options)) { + $form['file']['encrypt_config'] = array( + '#type' => 'select', + '#title' => t('Encryption Configuration'), + '#options' => encrypt_get_configs_as_options(), + '#default_value' => @$settings['encrypt_config'], + '#description' => t('Select a configuration to use for encryption.'), + '#states' => array( + 'visible' => array( + ':input[name="filters[encryption]"]' => array('value' => 'encrypt'), + ), + ), + ); + + } + return $form; } /** - * Return a list of backup filetypes. + * Returns a list of backup filetypes. */ - function file_types() { + public function file_types() { return array( "aes" => array( "extension" => "aes", @@ -72,29 +89,39 @@ class backup_migrate_filter_encryption extends backup_migrate_filter { "backup" => TRUE, "restore" => TRUE, ), + "encrypt" => array( + "extension" => "encrypt", + "filemime" => "application/octet-stream", + "backup" => TRUE, + "restore" => TRUE, + ), ); } /** - * Get the compression options as an options array for a form item. + * Gets the compression options as an options array for a form item. */ - function _backup_migrate_get_encryption_form_item_options() { + public function _backup_migrate_get_encryption_form_item_options() { $options = array(); $options = array('' => t('No Encryption')); if (@function_exists("aes_encrypt")) { $options['aes'] = t("AES Encryption"); } + if (@function_exists("encrypt")) { + $options['encrypt'] = t("Encryption With Encrypt Module"); + } return $options; } /** - * AES encrypt a file. + * AES encrypts a file. */ - function aes_encrypt($source, $dest) { + public function aes_encrypt($source, $dest) { $success = FALSE; if (function_exists('aes_encrypt')) { if ($data = $source->get_contents()) { - // Add a marker to the end of the data so we can trim the padding on decrpypt. + // Add a marker to the end of the data so we can trim the padding on + // decrpypt. $data = pack("a*H2", $data, "80"); if ($data = aes_encrypt($data, FALSE)) { $dest->put_contents($data); @@ -106,9 +133,9 @@ class backup_migrate_filter_encryption extends backup_migrate_filter { } /** - * Gzip decode a file. + * AES decodes a file. */ - function aes_decrypt($source, $dest) { + public function aes_decrypt($source, $dest) { $success = FALSE; if (function_exists('aes_decrypt')) { if ($data = $source->get_contents()) { @@ -124,10 +151,54 @@ class backup_migrate_filter_encryption extends backup_migrate_filter { } /** - * Compress a file with the given settings. - * Also updates settings to reflect new file mime and file extension. + * Encrypts a file with the Encrypt module. */ - function file_encrypt($file, $settings) { + public function encrypt($source, $dest, $settings) { + $success = FALSE; + if (function_exists('encrypt')) { + if (!empty($settings->filters['encrypt_config'])) { + $config_name = $settings->filters['encrypt_config']; + } + else { + $config = encrypt_get_default_config(); + $config_name = $config['name']; + } + if ($data = $source->get_contents()) { + // Add a marker to the end of the data so we can trim the padding on decrypt. + $data = pack("a*H2", $data, "80"); + if ($data = encrypt($data, array('base64' => FALSE), NULL, NULL, $config_name)) { + $dest->put_contents($data); + $success = TRUE; + } + } + } + return $success; + } + + /** + * Decrypts a file with the Encrypt module. + */ + public function decrypt($source, $dest) { + $success = FALSE; + if (function_exists('decrypt')) { + if ($data = $source->get_contents()) { + if ($data = decrypt($data)) { + // Trim all the padding zeros plus our non-zero marker. + $data = substr(rtrim($data, "\0"), 0, -1); + $dest->put_contents($data); + $success = TRUE; + } + } + } + return $success; + } + + /** + * Encrypts a file with the given settings. + * + * Also updates settings to reflect new file mime and file extension. + */ + public function file_encrypt($file, $settings) { if (!empty($settings->filters['encryption'])) { switch ($settings->filters['encryption']) { case "aes": @@ -137,6 +208,14 @@ class backup_migrate_filter_encryption extends backup_migrate_filter { $file = NULL; } break; + + case "encrypt": + $from = $file->push_type('encrypt'); + $from = new backup_file(array('filepath' => $from)); + if (!$success = $this->encrypt($from, $file, $settings)) { + $file = NULL; + } + break; } if (!$file) { _backup_migrate_message("Could not encrypt backup file. Try backing up without encryption.", array(), 'error'); @@ -146,10 +225,11 @@ class backup_migrate_filter_encryption extends backup_migrate_filter { } /** - * Decompress a file with the given settings. - * Also updates settings to reflect new file mime and file extension. + * Decrypts a file with the given settings. + * + * Also updates settings to reflect new file mime and file extension. */ - function file_decrypt($file) { + public function file_decrypt($file) { $success = FALSE; if ($file) { switch ($file->type_id()) { @@ -157,21 +237,39 @@ class backup_migrate_filter_encryption extends backup_migrate_filter { $from = $file->pop_type(); $success = $this->aes_decrypt($from, $file); break; + + case "encrypt": + $from = $file->pop_type(); + $success = $this->decrypt($from, $file); + break; + default: return $file; - break; - } - + } + if (!$success) { - if (function_exists('aes_decrypt')) { - _backup_migrate_message("Could not decrpyt backup file. Please check that the file is valid and that the encryption key of the server matches the server that created the backup.", array(), 'error'); - } - else { - _backup_migrate_message('You must install the !link to restore encrypted backkups.', array('!link' => l(t('AES Encryption Module'), 'http://drupal.org/project/aes')), 'error'); + switch ($file->type_id()) { + case 'aes': + if (function_exists('aes_decrypt')) { + _backup_migrate_message("Could not decrpyt backup file. Please check that the file is valid and that the encryption key of the server matches the server that created the backup.", array(), 'error'); + } + else { + _backup_migrate_message('You must install the !link to restore encrypted backkups.', array('!link' => l(t('AES Encryption Module'), 'http://drupal.org/project/aes')), 'error'); + } + break; + + case 'encrypt': + if (function_exists('decrypt')) { + _backup_migrate_message("Could not decrypt backup file. Please check that the file is valid and that the encryption key of the server matches the server that created the backup.", array(), 'error'); + } + else { + _backup_migrate_message('You must install the !link to restore encrypted backups.', array('!link' => l(t('Encrypt module'), 'http://drupal.org/project/encrypt')), 'error'); + } + break; } } } return $success ? $file : NULL; } -} +} diff --git a/sites/all/modules/contrib/admin/backup_migrate/includes/filters.inc b/sites/all/modules/contrib/admin/backup_migrate/includes/filters.inc index 679a25c..4b55f5d 100644 --- a/sites/all/modules/contrib/admin/backup_migrate/includes/filters.inc +++ b/sites/all/modules/contrib/admin/backup_migrate/includes/filters.inc @@ -1,25 +1,24 @@ array( - 'file' => drupal_get_path('module', 'backup_migrate') .'/includes/filters.backup_restore.inc', + 'file' => drupal_get_path('module', 'backup_migrate') . '/includes/filters.backup_restore.inc', 'class' => 'backup_migrate_filter_backup_restore', ), 'compression' => array( - 'file' => drupal_get_path('module', 'backup_migrate') .'/includes/filters.compression.inc', + 'file' => drupal_get_path('module', 'backup_migrate') . '/includes/filters.compression.inc', 'class' => 'backup_migrate_filter_compression', ), 'encryption' => array( - 'file' => drupal_get_path('module', 'backup_migrate') .'/includes/filters.encryption.inc', + 'file' => drupal_get_path('module', 'backup_migrate') . '/includes/filters.encryption.inc', 'class' => 'backup_migrate_filter_encryption', ), 'statusnotify' => array( - 'file' => drupal_get_path('module', 'backup_migrate') .'/includes/filters.statusnotify.inc', + 'file' => drupal_get_path('module', 'backup_migrate') . '/includes/filters.statusnotify.inc', 'class' => 'backup_migrate_filter_statusnotify', ), 'utils' => array( - 'file' => drupal_get_path('module', 'backup_migrate') .'/includes/filters.utils.inc', + 'file' => drupal_get_path('module', 'backup_migrate') . '/includes/filters.utils.inc', 'class' => 'backup_migrate_filter_utils', ), ); } /** - * Invoke the given method on all of the available filters. + * Invokes the given method on all of the available filters. */ function backup_migrate_filters_invoke_all() { $args = func_get_args(); @@ -96,42 +95,47 @@ function backup_migrate_filters_invoke_all() { } /** - * Filter a backup file before sending it to the destination. + * Filters a backup file before sending it to the destination. */ function backup_migrate_filters_backup($file, &$settings) { + backup_migrate_filters_invoke_all('pre_backup', $file, $settings); $filters = backup_migrate_get_filters('backup'); foreach ($filters as $filter) { if ($file) { $file = $filter->backup($file, $settings); } } + backup_migrate_filters_invoke_all('post_backup', $file, $settings); + return $file; } /** - * Filter a backup file before sending it to the destination. + * Filters a backup file before sending it to the destination. */ function backup_migrate_filters_restore($file, &$settings) { + backup_migrate_filters_invoke_all('pre_restore', $file, $settings); $filters = backup_migrate_get_filters('restore'); foreach ($filters as $filter) { if ($file) { $file = $filter->restore($file, $settings); } } + backup_migrate_filters_invoke_all('post_restore', $file, $settings); return $file; } /** - * Get the backup settings for all of the filters. + * Gets the backup settings for all of the filters. */ function backup_migrate_filters_settings_form($settings, $op) { - $out = backup_migrate_filters_invoke_all($op .'_settings_form', $settings); + $out = backup_migrate_filters_invoke_all($op . '_settings_form', $settings); $out = backup_migrate_filters_settings_form_set_parents($out); return $out; } /** - * Add a form parent to the filter settings so that the filter values are saved in the right table. + * Adds form parent to filter settings so the values are saved in correct table. */ function backup_migrate_filters_settings_form_set_parents($form) { foreach (element_children($form) as $key) { @@ -143,27 +147,51 @@ function backup_migrate_filters_settings_form_set_parents($form) { return $form; } - /** - * Validate all the filters. + * Validates all the filters. */ function backup_migrate_filters_settings_form_validate($op, $form, &$form_state) { - //backup_migrate_filters_invoke_all($op .'_settings_form_validate', $form, $form_state); + backup_migrate_filters_invoke_all($op . '_settings_form_validate', $form, $form_state); } /** - * Submit all of the filters. + * Submits all of the filters. */ function backup_migrate_filters_settings_form_submit($op, $form, &$form_state) { - //backup_migrate_filters_invoke_all($op .'_settings_form_submit', $form, $form_state); + backup_migrate_filters_invoke_all($op . '_settings_form_submit', $form, $form_state); } - /** - * Get the default settings for the filters. + * Gets the default settings for the filters. */ function backup_migrate_filters_settings_default($op) { - return backup_migrate_filters_invoke_all($op .'_settings_default'); + return backup_migrate_filters_invoke_all($op . '_settings_default'); +} + +/** + * Get the backup settings for all of the filters. + */ +function backup_migrate_filters_before_action_form($settings, $op) { + $out = array(); + $out += backup_migrate_filters_invoke_all('before_action_form', $op, $settings); + $out += backup_migrate_filters_invoke_all('before_' . $op . '_form', $settings); + return $out; +} + +/** + * Get the backup settings for all of the filters. + */ +function backup_migrate_filters_before_action_form_validate($settings, $op, $form, &$form_state) { + backup_migrate_filters_invoke_all('before_action_form_validate', $op, $settings, $form, $form_state); + backup_migrate_filters_invoke_all('before_' . $op . '_form_validate', $settings, $form, $form_state); +} + +/** + * Get the backup settings for all of the filters. + */ +function backup_migrate_filters_before_action_form_submit($settings, $op, $form, &$form_state) { + backup_migrate_filters_invoke_all('before_action_form_submit', $op, $settings, $form, $form_state); + backup_migrate_filters_invoke_all('before_' . $op . '_form_submit', $settings, $form, $form_state); } /** @@ -177,13 +205,13 @@ function backup_migrate_filters_file_types() { * A base class for basing filters on. */ class backup_migrate_filter { - var $weight = 0; - var $op_weights = array(); + public $weight = 0; + public $op_weights = array(); /** * Get the weight of the filter for the given op. */ - function weight($op = NULL) { + public function weight($op = NULL) { if ($op && isset($this->op_weights[$op])) { return $this->op_weights[$op]; } @@ -193,98 +221,110 @@ class backup_migrate_filter { /** * Get the form for the settings for this filter. */ - function backup_settings_default() { + public function backup_settings_default() { return array(); } /** * Get the form for the settings for this filter. */ - function backup_settings_form($settings) { + public function backup_settings_form($settings) { return array(); } /** * Get the form for the settings for this filter. */ - function backup_settings_form_validate($form, &$form_state) { + public function backup_settings_form_validate($form, &$form_state) { } /** * Submit the settings form. Any values returned will be saved. */ - function backup_settings_form_submit($form, &$form_state) { - return $form_state['values']; + public function backup_settings_form_submit($form, &$form_state) { } /** * Get the form for the settings for this filter. */ - function restore_settings_default() { + public function restore_settings_default() { return array(); } /** * Get the form for the settings for this filter. */ - function restore_settings_form($settings) { + public function restore_settings_form($settings) { return array(); } /** * Get the form for the settings for this filter. */ - function restore_settings_form_validate($form, &$form_state) { + public function restore_settings_form_validate($form, &$form_state) { } /** * Submit the settings form. Any values returned will be saved. */ - function restore_settings_form_submit($form, &$form_state) { + public function restore_settings_form_submit($form, &$form_state) { return $form_state['values']; } /** * Get a list of file types handled by this filter. */ - function file_types() { + public function file_types() { return array(); } /** * Declare any default destinations for this filter. */ - function destinations() { + public function destinations() { return array(); } - /** - * This function is called on a backup file after the backup has been completed. + * Called on a backup file after the backup has been completed. */ - function backup($file, &$settings) { - return $file; - } - - /** - * This function is called on a backup file before importing it. - */ - function restore($file, &$settings) { + public function backup($file, $settings) { return $file; } /** * This function is called immediately prior to backup. */ - function pre_backup($source, $file, $settings) { + public function pre_backup($file, $settings) { } /** * This function is called immediately post backup. */ - function post_backup($source, $file, $settings) { + public function post_backup($file, $settings) { } -} + /** + * This function is called on a backup file before importing it. + */ + public function restore($file, $settings) { + return $file; + } + + /** + * This function is called immediately prior to restore. + */ + public function pre_restore($file, $settings) { + + } + + /** + * This function is called immediately post restore. + */ + public function post_restore($file, $settings) { + + } + +} diff --git a/sites/all/modules/contrib/admin/backup_migrate/includes/filters.statusnotify.inc b/sites/all/modules/contrib/admin/backup_migrate/includes/filters.statusnotify.inc index 3371d6c..6101349 100644 --- a/sites/all/modules/contrib/admin/backup_migrate/includes/filters.statusnotify.inc +++ b/sites/all/modules/contrib/admin/backup_migrate/includes/filters.statusnotify.inc @@ -1,6 +1,5 @@ FALSE, 'notify_failure_enable' => FALSE, @@ -28,14 +27,20 @@ class backup_migrate_filter_statusnotify extends backup_migrate_filter { /** * Get the form for the settings for this filter. */ - function backup_settings_form($settings) { + public function backup_settings_form($settings) { $form = array(); $form['advanced']['notify_success_enable'] = array( "#type" => 'checkbox', "#title" => t("Send an email if backup succeeds"), "#default_value" => @$settings['notify_success_enable'], ); - $form['advanced']['notify_success_email'] = array( + $form['advanced']['notify_success_email_wrapper'] = array( + '#type' => 'backup_migrate_dependent', + '#dependencies' => array( + 'filters[notify_success_enable]' => TRUE, + ), + ); + $form['advanced']['notify_success_email_wrapper']['notify_success_email'] = array( "#type" => "textfield", "#title" => t("Email Address for Success Notices"), "#default_value" => @$settings['notify_success_email'], @@ -45,7 +50,13 @@ class backup_migrate_filter_statusnotify extends backup_migrate_filter { "#title" => t("Send an email if backup fails"), "#default_value" => @$settings['notify_failure_enable'], ); - $form['advanced']['notify_failure_email'] = array( + $form['advanced']['notify_failure_email_wrapper'] = array( + '#type' => 'backup_migrate_dependent', + '#dependencies' => array( + 'filters[notify_failure_enable]' => TRUE, + ), + ); + $form['advanced']['notify_failure_email_wrapper']['notify_failure_email'] = array( "#type" => "textfield", "#title" => t("Email Address for Failure Notices"), "#default_value" => @$settings['notify_failure_email'], @@ -56,41 +67,39 @@ class backup_migrate_filter_statusnotify extends backup_migrate_filter { /** * Send the success email. */ - function backup_succeed($settings) { + public function backup_succeed($settings) { if (@$settings->filters['notify_success_enable'] && $to = @$settings->filters['notify_success_email']) { $messages = $this->get_messages(); - $subject = t('!site backup succeeded', array('!site' => variable_get('site_name', 'Drupal site'))); if ($messages = $this->get_messages()) { $body = t("The site backup has completed successfully with the following messages:\n!messages", array('!messages' => $messages)); } else { $body = t("The site backup has completed successfully.\n"); } - mail($settings->filters['notify_success_email'], $subject, $body); + drupal_mail('backup_migrate', 'backup_succeed', $settings->filters['notify_success_email'], language_default(), array('body' => $body)); } } /** * Send the failure email. */ - function backup_fail($settings) { + public function backup_fail($settings) { if (@$settings->filters['notify_failure_enable'] && $to = @$settings->filters['notify_failure_email']) { $messages = $this->get_messages(); - $subject = t('!site backup failed', array('!site' => variable_get('site_name', 'Drupal site'))); if ($messages = $this->get_messages()) { $body = t("The site backup has failed with the following messages:\n!messages", array('!messages' => $messages)); } else { $body = t("The site backup has failed for an unknown reason."); } - mail($settings->filters['notify_failure_email'], $subject, $body); + drupal_mail('backup_migrate', 'backup_fail', $settings->filters['notify_failure_email'], language_default(), array('body' => $body)); } } /** * Render the messages and errors for the email. */ - function get_messages() { + public function get_messages() { $out = ""; $messages = _backup_migrate_messages(); foreach ($messages as $message) { @@ -98,5 +107,5 @@ class backup_migrate_filter_statusnotify extends backup_migrate_filter { } return $out; } -} +} diff --git a/sites/all/modules/contrib/admin/backup_migrate/includes/filters.utils.inc b/sites/all/modules/contrib/admin/backup_migrate/includes/filters.utils.inc index c4dce95..34b47d5 100644 --- a/sites/all/modules/contrib/admin/backup_migrate/includes/filters.utils.inc +++ b/sites/all/modules/contrib/admin/backup_migrate/includes/filters.utils.inc @@ -1,23 +1,23 @@ -1000, 'post_backup' => 1000); + public $saved_devel_query = NULL; /** * Get the default backup settings for this filter. */ - function backup_settings_default() { + public function backup_settings_default() { return array( 'utils_disable_query_log' => TRUE, 'utils_site_offline' => FALSE, @@ -28,7 +28,7 @@ class backup_migrate_filter_utils extends backup_migrate_filter { /** * Get the default restore settings for this filter. */ - function restore_settings_default() { + public function restore_settings_default() { return array( 'utils_disable_query_log' => TRUE, 'utils_site_offline' => FALSE, @@ -38,7 +38,7 @@ class backup_migrate_filter_utils extends backup_migrate_filter { /** * Get the form for the backup settings for this filter. */ - function backup_settings_form($settings) { + public function backup_settings_form($settings) { $form = array(); if (module_exists('devel') && variable_get('dev_query', 0)) { $form['database']['utils_disable_query_log'] = array( @@ -54,17 +54,35 @@ class backup_migrate_filter_utils extends backup_migrate_filter { '#default_value' => !empty($settings['utils_site_offline']) ? $settings['utils_site_offline'] : NULL, '#description' => t('Take the site offline during backup and show a maintenance message. Site will be taken back online once the backup is complete.'), ); - $form['advanced']['utils_site_offline_message'] = array( + $form['advanced']['utils_site_offline_message_wrapper'] = array( + '#type' => 'backup_migrate_dependent', + '#dependencies' => array( + 'filters[utils_site_offline]' => TRUE, + ), + ); + $form['advanced']['utils_site_offline_message_wrapper']['utils_site_offline_message'] = array( '#type' => 'textarea', '#title' => t('Site off-line message'), - '#default_value' => !empty($settings['utils_site_offline_message']) ? $settings['utils_site_offline_message'] : variable_get('maintenance_mode_message', t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal')))), - '#description' => t('Message to show visitors when the site is in off-line mode.') + '#default_value' => !empty($settings['utils_site_offline_message']) ? $settings['utils_site_offline_message'] : variable_get('site_offline_message', t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal')))), + '#description' => t('Message to show visitors when the site is in off-line mode.'), ); $form['advanced']['utils_description'] = array( '#type' => 'textarea', - '#title' => t('Description'), + '#title' => t('Add a note'), '#default_value' => !empty($settings['utils_description']) ? $settings['utils_description'] : NULL, - '#description' => t('Add a short description to the backup file.'), + '#description' => t('Add a short note to the backup file.'), + ); + $form['advanced']['use_cli'] = array( + "#type" => "checkbox", + "#title" => t("Use cli commands"), + "#default_value" => !empty($settings['use_cli']), + "#description" => t("Use the command line tools (mysqldump, tar, gzip etc.) if available. This can be faster for large sites but will not work on all servers. EXPERIMENTAL"), + ); + $form['advanced']['ignore_errors'] = array( + "#type" => "checkbox", + "#title" => t("Ignore errors"), + "#default_value" => !empty($settings['ignore_errors']), + "#description" => t("Will attempt to complete backup even if certain recoverable errors occur. This may make the backup files invalid. Enable this if you have unreadable files that you want to ignore during backup."), ); return $form; @@ -73,7 +91,7 @@ class backup_migrate_filter_utils extends backup_migrate_filter { /** * Get the form for the restore settings for this filter. */ - function restore_settings_form($settings) { + public function restore_settings_form($settings) { $form = array(); if (module_exists('devel') && variable_get('dev_query', 0)) { $form['advanced']['utils_disable_query_log'] = array( @@ -89,32 +107,70 @@ class backup_migrate_filter_utils extends backup_migrate_filter { '#default_value' => !empty($settings['utils_site_offline']) ? $settings['utils_site_offline'] : NULL, '#description' => t('Take the site offline during restore and show a maintenance message. Site will be taken back online once the restore is complete.'), ); - $form['advanced']['utils_site_offline_message'] = array( + $form['advanced']['utils_site_offline_message_wrapper'] = array( + '#type' => 'backup_migrate_dependent', + '#dependencies' => array( + 'filters[utils_site_offline]' => TRUE, + ), + ); + $form['advanced']['utils_site_offline_message_wrapper']['utils_site_offline_message'] = array( '#type' => 'textarea', '#title' => t('Site off-line message'), - '#default_value' => !empty($settings['utils_site_offline_message']) ? $settings['utils_site_offline_message'] : variable_get('maintenance_mode_message', t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal')))), - '#description' => t('Message to show visitors when the site is in off-line mode.') + '#default_value' => !empty($settings['utils_site_offline_message']) ? $settings['utils_site_offline_message'] : variable_get('site_offline_message', t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal')))), + '#description' => t('Message to show visitors when the site is in off-line mode.'), ); + $form['advanced']['utils_drop_all_tables'] = array( + '#type' => 'checkbox', + '#title' => t('Drop all tables before import (MySQL only)'), + '#default_value' => !empty($settings['utils_drop_all_tables']) ? $settings['utils_drop_all_tables'] : NULL, + '#description' => t('Drop all existing database tables before restoring the backup. This option is currently available on MySQL servers only.'), + ); + $form['advanced']['use_cli'] = array( + "#type" => "checkbox", + "#title" => t("Use cli commands"), + "#default_value" => !empty($settings['use_cli']), + "#description" => t("Use the command line tools (mysqldump, tar, gzip etc.) if available. This can be faster for large sites but will not work on all servers. EXPERIMENTAL"), + ); + // $form['advanced']['ignore_errors'] = array( + // "#type" => "checkbox", + // "#title" => t("Ignore errors"), + // "#default_value" => !empty($settings['ignore_errors']), + // "#description" => t("Will attempt to complete restore even if certain recoverable errors occur. This may could corrupt your site."), + // ); return $form; } - function pre_backup($source, $file, $settings) { + /** + * + */ + public function pre_backup($file, $settings) { $this->take_site_offline($settings); $this->disable_devel_query($settings); } - function post_backup($source, $file, $settings) { + /** + * + */ + public function post_backup($file, $settings) { $this->enable_devel_query($settings); $this->take_site_online($settings); - $this->add_file_info($file, $settings); + if ($file) { + $this->add_file_info($file, $settings); + } } - function pre_restore($file, $settings) { + /** + * + */ + public function pre_restore($file, $settings) { $this->disable_devel_query($settings); $this->take_site_offline($settings); } - function post_restore($file, $settings) { + /** + * + */ + public function post_restore($file, $settings) { $this->enable_devel_query($settings); $this->take_site_online($settings); } @@ -122,7 +178,7 @@ class backup_migrate_filter_utils extends backup_migrate_filter { /** * Disable devel query logging if it's active and the user has chosen to do so. */ - function disable_devel_query($settings) { + public function disable_devel_query($settings) { $this->saved_devel_query = variable_get('dev_query', 0); if (module_exists('devel') && variable_get('dev_query', 0) && !empty($settings->filters['utils_disable_query_log'])) { variable_set('dev_query', 0); @@ -132,7 +188,7 @@ class backup_migrate_filter_utils extends backup_migrate_filter { /** * Restore devel query to previous state. */ - function enable_devel_query($settings) { + public function enable_devel_query($settings) { if (module_exists('devel')) { variable_set('dev_query', $this->saved_devel_query); } @@ -141,49 +197,61 @@ class backup_migrate_filter_utils extends backup_migrate_filter { /** * Add the backup metadata to the file. */ - function add_file_info($file, $settings) { - $file->file_info['description'] = $settings->filters['utils_description']; - $file->file_info['datestamp'] = time(); - $file->file_info['generator'] = 'Backup and Migrate (http://drupal.org/project/backup_migrate)'; - $file->file_info['generatorversion'] = BACKUP_MIGRATE_VERSION; - $file->file_info['sites'] = array( - '0' => array( - 'version' => VERSION, - 'name' => variable_get('site_name', ''), - 'url' => url('', array('absolute' => TRUE)), - ), - ); - + public function add_file_info($file, $settings) { + $file->file_info['description'] = $settings->filters['utils_description']; + $file->file_info['datestamp'] = time(); + $file->file_info['generator'] = 'Backup and Migrate (http://drupal.org/project/backup_migrate)'; + $file->file_info['generatorversion'] = BACKUP_MIGRATE_VERSION; + $file->file_info['siteurl'] = url('', array('absolute' => TRUE)); + $file->file_info['sitename'] = variable_get('site_name', ''); + $file->file_info['drupalversion'] = VERSION; + $file->calculate_filesize(); + + $source = $settings->get('source'); + $file->file_info['bam_sourceid'] = $source->get('id'); + $file->file_info['bam_sourcetype'] = $source->get('subtype'); + $file->file_info['bam_sourcename'] = $source->get('name'); + + // Add any additional info that has been added to the settings by other plugins. + if (!empty($settings->file_info)) { + $file->file_info += $settings->file_info; + } } /** * Take the site offline if configured to do so. */ - function take_site_offline($settings) { - // Save the current state of the site in case a restore overwrites it. - $this->saved_site_offline = variable_get('maintenance_mode', 0); - if (@$settings->filters['utils_site_offline']) { - $this->saved_site_offline_message = variable_get('maintenance_mode_message', NULL); - if (!empty($settings->filters['utils_site_offline_message'])) { + public function take_site_offline($settings) { + // If the site is already offline then don't do anything. + if (!variable_get('maintenance_mode', 0)) { + // Save the current state of the site in case a restore overwrites it. + if (!empty($settings->filters['utils_site_offline'])) { + $this->saved_site_offline = TRUE; $this->saved_site_offline_message = variable_get('maintenance_mode_message', NULL); - variable_set('maintenance_mode_message', $settings->filters['utils_site_offline_message']); + if (!empty($settings->filters['utils_site_offline_message'])) { + $this->saved_site_offline_message = variable_get('maintenance_mode_message', NULL); + variable_set('maintenance_mode_message', $settings->filters['utils_site_offline_message']); + } + variable_set('maintenance_mode', 1); + _backup_migrate_message('Site was taken offline.'); } - variable_set('maintenance_mode', 1); - _backup_migrate_message('Site was taken offline.'); } } /** * Take the site online again after backup or restore. */ - function take_site_online($settings) { + public function take_site_online($settings) { // Take the site back off/online because the restored db may have changed that setting. - variable_set('maintenance_mode', $this->saved_site_offline); - if ($settings->filters['utils_site_offline']) { - if (!empty($this->saved_site_offline_message)) { - variable_set('maintenance_mode_message', $this->saved_site_offline_message); + if (variable_get('maintenance_mode', 0) && !empty($this->saved_site_offline)) { + variable_set('maintenance_mode', 0); + if ($settings->filters['utils_site_offline']) { + if (!empty($this->saved_site_offline_message)) { + variable_set('maintenance_mode_message', $this->saved_site_offline_message); + } + _backup_migrate_message('Site was taken online.'); } - _backup_migrate_message('Site was taken online.'); } } -} \ No newline at end of file + +} diff --git a/sites/all/modules/contrib/admin/backup_migrate/includes/locations.inc b/sites/all/modules/contrib/admin/backup_migrate/includes/locations.inc new file mode 100644 index 0000000..2072e32 --- /dev/null +++ b/sites/all/modules/contrib/admin/backup_migrate/includes/locations.inc @@ -0,0 +1,529 @@ + $location) { + if ($location->op($op)) { + $out[$key] = $location; + } + } + } + return $out; +} + +/** + * Get the location of the given id. + */ +function backup_migrate_get_location($id) { + $locations = backup_migrate_get_locations('all'); + return empty($locations[$id]) ? NULL : $locations[$id]; +} + +/** + * A base class for creating locations. + */ +class backup_migrate_location extends backup_migrate_item { + public $db_table = "backup_migrate_destinations"; + public $type_name = "location"; + public $default_values = array('settings' => array()); + public $singular = 'location'; + public $plural = 'locations'; + public $title_plural = 'Locations'; + public $title_singular = 'Location'; + + public $subtype = ""; + public $supported_ops = array(); + + /** + * This function is not supposed to be called. It is just here to help the po extractor out. + */ + public function strings() { + // Help the pot extractor find these strings. + t('location'); + t('locations'); + t('Location'); + t('Locations'); + } + + /** + * + */ + public function ops() { + return $this->supported_ops; + } + + /** + * Does this location support the given operation. + */ + public function op($op) { + $ops = (array) $this->ops(); + return in_array($op, $ops); + } + + /** + * Remove the given op from the support list. + */ + public function remove_op($op) { + $key = array_search($op, $this->supported_ops); + if ($key !== FALSE) { + unset($this->supported_ops[$key]); + } + } + + /** + * + */ + public function get_name() { + return @$this->name; + } + + /** + * + */ + public function set_name($name) { + return $this->name = $name; + } + + /** + * + */ + public function set_location($location) { + $this->location = $location; + } + + /** + * + */ + public function get_location() { + return @$this->location; + } + + /** + * + */ + public function get_display_location() { + return $this->get_location(); + } + + /** + * + */ + public function settings($key = NULL) { + $out = $this->settings; + if ($key) { + $out = isset($out[$key]) ? $out[$key] : NULL; + } + return $out; + } + + /** + * Get the type name of this location for display to the user. + */ + public function get_subtype_name() { + if ($type = $this->get('subtype')) { + $types = $this->location_types(); + return isset($types[$type]['type_name']) ? $types[$type]['type_name'] : $type; + } + } + + /** + * Get the edit form for the item. + */ + public function edit_form() { + if (!empty($this->supported_ops)) { + $form = parent::edit_form(); + $form['subtype'] = array( + "#type" => "value", + "#default_value" => $this->get('subtype'), + ); + } + else { + $types = $this->location_types(); + $items = array(); + // If no (valid) node type has been provided, display a node type overview. + foreach ($types as $key => $type) { + if (@$type['can_create']) { + $type_url_str = str_replace('_', '-', $key); + $out = '
' . l($type['type_name'], BACKUP_MIGRATE_MENU_PATH . "/settings/$this->type_name/add/$type_url_str", array('attributes' => array('title' => t('Add a new @s location.', array('@s' => $type['type_name']))))) . '
'; + $out .= '
' . filter_xss_admin($type['description']) . '
'; + $items[] = $out; + } + } + if (count($items)) { + $output = t('Choose the type of location you would like to create:') . '
' . implode('', $items) . '
'; + } + else { + $output = t('No types available.'); + } + $form['select_type'] = array( + '#type' => 'markup', + '#markup' => $output, + ); + } + return $form; + } + + /** + * Get the available location types. + */ + public function location_types() { + return backup_migrate_get_location_subtypes(); + } + + /** + * Get the message to send to the user when confirming the deletion of the item. + */ + public function delete_confirm_message() { + return t('Are you sure you want to delete the %name?', array('%name' => $this->get_name())); + } + + /** + * Get the columns needed to list the type. + */ + public function get_list_column_info() { + $out = parent::get_list_column_info(); + $out = array( + 'name' => array('title' => t('Name')), + 'subtype_name' => array('title' => t('Type')), + 'display_location' => array('title' => t('Location')), + ) + $out; + return $out; + } + + /** + * Get a row of data to be used in a list of items of this type. + */ + public function get_list_row() { + $out = parent::get_list_row(); + + // Suppress locations with no actions as there's no value in showing them (and they may confuse new users). + if (empty($out['actions'])) { + return NULL; + } + return $out; + } + + /** + * Get the action links for a location. + */ + public function get_action_links() { + $out = parent::get_action_links(); + $item_id = $this->get_id(); + + // Don't display the download/delete/restore ops if they are not available for this location. + if ($this->op('list files') && user_access("access backup files")) { + $out = array('list files' => l(t("list files"), BACKUP_MIGRATE_MENU_PATH . "/$this->type_name/list/files/" . $item_id)) + $out; + } + if (!$this->op('configure') || !user_access('administer backup and migrate')) { + unset($out['edit']); + } + return $out; + } + + /** + * Determine if we can read the given file. + */ + public function can_read_file($file_id) { + return $this->op('restore'); + } + + /** + * Get the form for the settings for this location type. + */ + public function settings_default() { + return array(); + } + + /** + * Get the form for the settings for this location. + */ + public function settings_form($form) { + return $form; + } + + /** + * Validate the form for the settings for this location. + */ + public function settings_form_validate($form_values) { + } + + /** + * Submit the settings form. Any values returned will be saved. + */ + public function settings_form_submit($form_values) { + return $form_values; + } + + /** + * Get the form for the settings for this filter. + */ + public function backup_settings_default() { + return array(); + } + + /** + * Get the form for the settings for this filter. + */ + public function backup_settings_form($settings) { + return array(); + } + + /** + * Get the form for the settings for this filter. + */ + public function backup_settings_form_validate($form, &$form_state) { + } + + /** + * Submit the settings form. Any values returned will be saved. + */ + public function backup_settings_form_submit($form, &$form_state) { + } + + /** + * Get the form for the settings for this filter. + */ + public function restore_settings_default() { + return array(); + } + + /** + * Get the form for the settings for this filter. + */ + public function restore_settings_form($settings) { + return array(); + } + + /** + * Get the form for the settings for this filter. + */ + public function restore_settings_form_validate($form_values) { + } + + /** + * Submit the settings form. Any values returned will be saved. + */ + public function restore_settings_form_submit($form_values) { + return $form_values; + } + + /** + * Create a new location of the correct type. + */ + public function create($params = array()) { + $out = NULL; + $types = backup_migrate_get_location_subtypes(); + // Get the type passed in in the params, or if none, check the url for a valid type name. + // This is to allow new location type to be specified in the path. + $location_type = !empty($params['subtype']) ? $params['subtype'] : NULL; + + if ($location_type && ($type = @$types[$location_type])) { + // Include the necessary file if specified by the type. + if (!empty($type['file'])) { + require_once './' . $type['file']; + } + $out = new $type['class']($params + array('subtype' => $location_type)); + } + + if (empty($out)) { + $out = parent::create($params); + } + return $out; + } + + /** + * Get a url from the parts. + */ + public function url($hide_password = TRUE) { + return $this->glue_url($this->dest_url, $hide_password); + } + + /** + * Glue a URLs component parts back into a URL. + */ + public function glue_url($parts, $hide_password = TRUE) { + // Obscure the password if we need to. + $parts['pass'] = $hide_password ? "" : $parts['pass']; + + // Assemble the URL. + $out = ""; + $out .= $parts['scheme'] . '://'; + $out .= $parts['user'] ? urlencode($parts['user']) : ''; + $out .= ($parts['user'] && $parts['pass']) ? ":" . urlencode($parts['pass']) : ''; + $out .= ($parts['user'] || $parts['pass']) ? "@" : ""; + $out .= $parts['host']; + $out .= !empty($parts['port']) ? ':' . $parts['port'] : ''; + $out .= "/" . $parts['path']; + return $out; + } + + /** + * Break a URL into it's component parts. + */ + public function set_url($url) { + $parts = (array) parse_url($url); + $parts['user'] = urldecode(@$parts['user']); + $parts['pass'] = urldecode(@$parts['pass']); + $parts['path'] = urldecode(@$parts['path']); + $parts['path'] = ltrim(@$parts['path'], "/"); + $this->dest_url = $parts; + } + + /** + * Retrieve a list of filetypes supported by this source/destination. + */ + public function file_types() { + return array(); + } + +} + +/** + * A base class for creating locations. + */ +class backup_migrate_location_remote extends backup_migrate_location { + + /** + * The location is a URI so parse it and store the parts. + */ + public function get_location() { + return $this->url(FALSE); + } + + /** + * The location to display is the url without the password. + */ + public function get_display_location() { + return $this->url(TRUE); + } + + /** + * Return the location with the password. + */ + public function set_location($location) { + $this->location = $location; + $this->set_url($location); + } + + /** + * Location configuration callback. + */ + public function edit_form() { + $form = parent::edit_form(); + $form['scheme'] = array( + "#type" => "select", + "#title" => t("Scheme"), + "#default_value" => @$this->dest_url['scheme'] ? $this->dest_url['scheme'] : 'mysql', + "#required" => TRUE, + "#options" => array($GLOBALS['db_type'] => $GLOBALS['db_type']), + "#weight" => 0, + ); + $form['host'] = array( + "#type" => "textfield", + "#title" => t("Host"), + "#default_value" => @$this->dest_url['host'] ? $this->dest_url['host'] : 'localhost', + "#required" => TRUE, + "#weight" => 10, + ); + $form['path'] = array( + "#type" => "textfield", + "#title" => t("Path"), + "#default_value" => @$this->dest_url['path'], + "#required" => TRUE, + "#weight" => 20, + ); + $form['user'] = array( + "#type" => "textfield", + "#title" => t("Username"), + "#default_value" => @$this->dest_url['user'], + "#required" => TRUE, + "#weight" => 30, + ); + $form['pass'] = array( + "#type" => "password", + "#title" => t("Password"), + "#default_value" => @$this->dest_url['pass'], + '#description' => '', + "#weight" => 40, + ); + if (@$this->dest_url['pass']) { + $form['old_password'] = array( + "#type" => "value", + "#value" => @$this->dest_url['pass'], + ); + $form['pass']["#description"] .= t(' You do not need to enter a password unless you wish to change the currently saved password.'); + } + return $form; + } + + /** + * Submit the configuration form. Glue the url together and add the old password back if a new one was not specified. + */ + public function edit_form_submit($form, &$form_state) { + $form_state['values']['pass'] = $form_state['values']['pass'] ? $form_state['values']['pass'] : $form_state['values']['old_password']; + $form_state['values']['location'] = $this->glue_url($form_state['values'], FALSE); + parent::edit_form_submit($form, $form_state); + } + +} diff --git a/sites/all/modules/contrib/admin/backup_migrate/includes/profiles.inc b/sites/all/modules/contrib/admin/backup_migrate/includes/profiles.inc index da49b21..ba37321 100644 --- a/sites/all/modules/contrib/admin/backup_migrate/includes/profiles.inc +++ b/sites/all/modules/contrib/admin/backup_migrate/includes/profiles.inc @@ -1,19 +1,35 @@ array( + 'include' => 'profiles', + 'type_name' => t('Backup Settings Profile'), + 'class' => 'backup_migrate_profile', + ), + ); + return $out; +} /** * Get all the available backup profiles. */ function backup_migrate_get_profiles() { - backup_migrate_include('filters'); - static $profiles = NULL; + require_once dirname(__FILE__) . '/filters.inc'; + + $profiles = &drupal_static('backup_migrate_get_profiles', NULL); // Get the list of profiles and cache them locally. if ($profiles === NULL) { @@ -23,14 +39,14 @@ function backup_migrate_get_profiles() { } /** - * Implementation of hook_backup_migrate_profiles_alter(). + * Implements hook_backup_migrate_profiles_alter(). * * Add default settings for any plugins which didn't exist when the profile was saved. */ function backup_migrate_backup_migrate_profiles_alter(&$profiles) { foreach ($profiles as $id => $profile) { // Set the default values for filter setting which don't exist in the profile. - $profiles[$id]->filters = (array)@$profile->filters + (array)backup_migrate_filters_settings_default('backup'); + $profiles[$id]->filters = (array) @$profile->filters + (array) backup_migrate_filters_settings_default('backup'); } } @@ -43,13 +59,13 @@ function backup_migrate_get_profile($profile_id) { } /** - * Implementation of hook_backup_migrate_profiles(). + * Implements hook_backup_migrate_profiles(). */ function backup_migrate_backup_migrate_profiles() { $out = array(); // Get the module default profile. - $out['default'] = backup_migrate_crud_create_item('profile', array('name' => t("Default Settings"), 'profile_id' => 'default')); + $out['default'] = backup_migrate_crud_create_item('profile', array('name' => t("Default Settings"), 'machine_name' => 'default')); return $out; } @@ -61,7 +77,7 @@ function backup_migrate_backup_migrate_profiles() { */ function _backup_migrate_get_profile_form_item_options() { $out = array(); - foreach ((array)backup_migrate_get_profiles() as $key => $profile) { + foreach ((array) backup_migrate_get_profiles() as $key => $profile) { $out[$key] = $profile->get('name'); } return $out; @@ -71,13 +87,15 @@ function _backup_migrate_get_profile_form_item_options() { * Get a form to configure the profile. */ function _backup_migrate_ui_backup_settings_form($profile) { + require_once dirname(__FILE__) . '/destinations.inc'; + require_once dirname(__FILE__) . '/files.inc'; + require_once dirname(__FILE__) . '/filters.inc'; + drupal_add_js(array('backup_migrate' => array('checkboxLinkText' => t('View as checkboxes'))), array('type' => 'setting')); - drupal_add_js(drupal_get_path('module', 'backup_migrate') .'/backup_migrate.js', array('type' => 'file', 'scope' => 'footer')); - drupal_add_css(drupal_get_path('module', 'backup_migrate') .'/backup_migrate.css'); + drupal_add_js(drupal_get_path('module', 'backup_migrate') . '/backup_migrate.js', array('type' => 'file', 'scope' => 'footer')); + drupal_add_css(drupal_get_path('module', 'backup_migrate') . '/backup_migrate.css'); - backup_migrate_include('files', 'destinations', 'filters'); - - $form = array(); + $form = array(); $form['file'] = array( "#type" => "fieldset", @@ -92,8 +110,6 @@ function _backup_migrate_ui_backup_settings_form($profile) { "#default_value" => $profile->filename, ); if (module_exists('token')) { - $form['file']['filename']['#description'] = t('You can use tokens in the file name.'); - $form['file']['token_help'] = array( '#title' => t('Replacement patterns'), '#type' => 'fieldset', @@ -105,14 +121,26 @@ function _backup_migrate_ui_backup_settings_form($profile) { '#token_types' => array('current-date', 'site'), '#global_types' => FALSE, ); + $form['file']['filename']['#description'] = t('You can use tokens in the file name.'); } $form['file']['append_timestamp'] = array( - "#type" => "checkbox", - "#title" => t("Append a timestamp."), + "#type" => "radios", + '#options' => array( + 0 => t('Create separate backups if `Backup file name` already exists'), + 2 => t('Overwrite the existing backup file'), + 1 => t('Append the timestamp'), + ), + "#title" => t("Save mode"), "#default_value" => $profile->append_timestamp, ); - $form['file']['timestamp_format'] = array( + $form['file']['timestamp_format_wrapper'] = array( + '#type' => 'backup_migrate_dependent', + '#dependencies' => array( + 'append_timestamp' => 1, + ), + ); + $form['file']['timestamp_format_wrapper']['timestamp_format'] = array( "#type" => "textfield", "#title" => t("Timestamp format"), "#default_value" => $profile->timestamp_format, @@ -126,12 +154,12 @@ function _backup_migrate_ui_backup_settings_form($profile) { if ($form['advanced']) { $form['advanced']['#type'] = 'fieldset'; $form['advanced']['#title'] = t('Advanced Options'); - $form['advanced']['#collapsed'] = true; - $form['advanced']['#collapsible'] = true; + $form['advanced']['#collapsed'] = TRUE; + $form['advanced']['#collapsible'] = TRUE; } - $form['#validate'][] = '_backup_migrate_ui_backup_settings_form_validate'; - $form['#submit'][] = '_backup_migrate_ui_backup_settings_form_submit'; + $form['#validate'][] = '_backup_migrate_ui_backup_settings_form_validate'; + $form['#submit'][] = '_backup_migrate_ui_backup_settings_form_submit'; return $form; } @@ -149,12 +177,14 @@ function _backup_migrate_ui_backup_settings_form_validate($form, &$form_state) { function _backup_migrate_ui_backup_settings_form_submit($form, &$form_state) { backup_migrate_filters_settings_form_submit('backup', $form, $form_state); } - + /** * Get the default profile. */ function _backup_migrate_profile_default_profile() { - backup_migrate_include('files', 'filters'); + require_once dirname(__FILE__) . '/files.inc'; + require_once dirname(__FILE__) . '/filters.inc'; + return array( 'source_id' => 'db', 'filename' => _backup_migrate_default_filename(), @@ -183,41 +213,62 @@ function _backup_migrate_profile_saved_default_profile($profile_id = NULL) { * A profile class for crud operations. */ class backup_migrate_profile extends backup_migrate_item { - var $db_table = "backup_migrate_profiles"; - var $type_name = "profile"; - var $singular = 'profile'; - var $plural = 'profiles'; + public $db_table = "backup_migrate_profiles"; + public $type_name = "profile"; + public $singular = 'settings profile'; + public $plural = 'settings profiles'; + public $title_plural = 'Settings Profiles'; + public $title_singular = 'Settings Profile'; + + /** + * Perform a shallow merge of the defaults and the parameters. + * + * This is needed because otherwise it will *combine* the nested arrays and + * make it impossible to deselect database tables from the 'nodata' setting. + * + * @param array $params + */ + public function __construct(array $params = array()) { + $params = (array) $params; + $defaults = (array) $this->get_default_values(); + foreach ($defaults as $key => $val) { + if (!isset($params[$key])) { + $params[$key] = $val; + } + } + $this->from_array($params); + } /** * This function is not supposed to be called. It is just here to help the po extractor out. */ - function strings() { + public function strings() { // Help the pot extractor find these strings. - t('Profile'); - t('Profiles'); - t('profile'); - t('profiles'); + t('Settings Profile'); + t('Settings Profiles'); + t('settings profile'); + t('settings profiles'); } /** * Get the default values for standard parameters. */ - function get_default_values() { + public function get_default_values() { return _backup_migrate_profile_default_profile() + array('name' => t("Untitled Profile")); } /** * Get a table of all items of this type. - */ - function get_list() { - drupal_add_css(drupal_get_path('module', 'backup_migrate') .'/backup_migrate.css'); + */ + public function get_list() { + drupal_add_css(drupal_get_path('module', 'backup_migrate') . '/backup_migrate.css'); return parent::get_list(); } /** * Get the columns needed to list the type. - */ - function get_list_column_info() { + */ + public function get_list_column_info() { $out = parent::get_list_column_info(); $out = array( 'name' => array('title' => t('Name')), @@ -227,23 +278,10 @@ class backup_migrate_profile extends backup_migrate_item { return $out; } - /** - * Get a row of data to be used in a list of items of this type. - */ - function get_list_row() { - $row = parent::get_list_row(); - if (empty($this->enabled)) { - foreach ($row as $key => $field) { - $row[$key] = array('data' => $field, 'class' => 'profile-list-disabled'); - } - } - return $row; - } - /** * Set the source of this setings profile. Takes either a source object or source id. */ - function set_source($source) { + public function set_source($source) { if (is_object($source)) { $this->source = $source; $this->source_id = $source->get_id(); @@ -257,10 +295,11 @@ class backup_migrate_profile extends backup_migrate_item { /** * Get the source of the profile. */ - function get_source() { - backup_migrate_include('destinations'); - if (!empty($this->source_id) && (empty($this->source) || $this->source->destination_id !== $this->source_id)) { - $this->source = backup_migrate_get_destination($this->source_id); + public function get_source() { + require_once dirname(__FILE__) . '/locations.inc'; + + if (!empty($this->source_id) && (empty($this->source) || $this->source->get_id() !== $this->source_id)) { + $this->source = backup_migrate_get_source($this->source_id); } return empty($this->source) ? NULL : $this->source; } @@ -268,7 +307,7 @@ class backup_migrate_profile extends backup_migrate_item { /** * Get the name of the source. */ - function get_source_name() { + public function get_source_name() { if ($source = $this->get_source()) { return $source->get_name(); } @@ -278,28 +317,64 @@ class backup_migrate_profile extends backup_migrate_item { /** * Get the destination of the profile. */ - function get_destination() { - backup_migrate_include('destinations'); - if (!empty($this->destination_id) && (empty($this->destination) || $this->destination->destination_id !== $this->destination_id)) { - $this->destination = backup_migrate_get_destination($this->destination_id); + public function get_destination() { + $destinations = (array) $this->get_destinations(); + return reset($destinations); + } + + /** + * Get the destination of the profile. + */ + public function get_destinations() { + require_once dirname(__FILE__) . '/destinations.inc'; + + if (empty($this->destinations)) { + $this->destinations = array(); + $ids = $weights = array(); + if (!empty($this->destination_id)) { + foreach ((array) $this->destination_id as $destination_id) { + if (!in_array($destination_id, $ids) && $destination = backup_migrate_get_destination($destination_id)) { + $this->destinations[] = $destination; + $weights[] = $destination->get('weight'); + $ids[] = $destination_id; + } + } + } + // Sort the destinations by weight. + array_multisort($weights, SORT_NUMERIC, $this->destinations); } - return empty($this->destination) ? NULL : $this->destination; + + return $this->destinations; } /** * Get the name of the destination. */ - function get_destination_name() { - if ($destination = $this->get_destination()) { - return $destination->get_name(); + public function get_destination_name() { + $out = array(); + foreach ($this->get_destinations() as $destination) { + $out[] = $destination->get_name(); + } + if ($out) { + return implode(', ', $out); } return t("Missing"); } + /** + * Get the source and destinations specified in the given settings profile. + */ + public function get_all_locations() { + $out = array(); + $out += $this->get('destinations'); + $out[] = $this->get('source'); + return $out; + } + /** * Get the edit form. */ - function edit_form() { + public function edit_form() { $form = parent::edit_form(); $form['name'] = array( "#type" => "textfield", @@ -314,8 +389,8 @@ class backup_migrate_profile extends backup_migrate_item { /** * Get the message to send to the user when confirming the deletion of the item. */ - function delete_confirm_message() { + public function delete_confirm_message() { return t('Are you sure you want to delete the profile %name? Any schedules using this profile will be disabled.', array('%name' => $this->get('name'))); } -} +} diff --git a/sites/all/modules/contrib/admin/backup_migrate/includes/schedules.inc b/sites/all/modules/contrib/admin/backup_migrate/includes/schedules.inc index 456a149..0415894 100644 --- a/sites/all/modules/contrib/admin/backup_migrate/includes/schedules.inc +++ b/sites/all/modules/contrib/admin/backup_migrate/includes/schedules.inc @@ -5,24 +5,82 @@ * All of the schedule handling code needed for Backup and Migrate. */ -backup_migrate_include('crud'); +define('BACKUP_MIGRATE_KEEP_ALL', 0); +define('BACKUP_MIGRATE_STANDARD_DELETE', -1); +define('BACKUP_MIGRATE_SMART_DELETE', -2); + +define('BACKUP_MIGRATE_CRON_BUILTIN', 'builtin'); +define('BACKUP_MIGRATE_CRON_ELYSIA', 'elysia'); +define('BACKUP_MIGRATE_CRON_NONE', 'none'); + + +define('BACKUP_MIGRATE_SMART_KEEP_SUBHOURLY', 3600); +define('BACKUP_MIGRATE_SMART_KEEP_HOURLY', 24); +define('BACKUP_MIGRATE_SMART_KEEP_DAILY', 30); +define('BACKUP_MIGRATE_SMART_KEEP_WEEKLY', PHP_INT_MAX); + +define('BACKUP_MIGRATE_KEEP_DEFAULT', 100); + +require_once dirname(__FILE__) . '/crud.inc'; + +/** + * Implements hook_backup_migrate_destination_types(). + * + * Get the built in Backup and Migrate destination types. + */ +function backup_migrate_backup_migrate_schedule_types() { + $out = array(); + $out += array( + 'backup' => array( + 'include' => 'schedule', + 'type_name' => t('Backup Schedule'), + 'class' => 'backup_migrate_schedule', + ), + ); + return $out; +} /** * Run the preconfigured schedules. Called on cron. */ -function backup_migrate_schedules_run() { - backup_migrate_include('profiles'); +function backup_migrate_schedules_cron() { + require_once dirname(__FILE__) . '/profiles.inc'; + foreach (backup_migrate_get_schedules() as $schedule) { $schedule->cron(); } backup_migrate_cleanup(); } +/** + * Run the preconfigured schedules regardless of scheduled time settings. + */ +function backup_migrate_schedules_run() { + require_once dirname(__FILE__) . '/profiles.inc'; + + foreach (backup_migrate_get_schedules() as $schedule) { + $schedule->run(); + } + backup_migrate_cleanup(); +} + +/** + * Run the preconfigured schedules. Called on cron. + */ +function backup_migrate_schedule_run($schedule_id) { + require_once dirname(__FILE__) . '/profiles.inc'; + + if (($schedule = backup_migrate_get_schedule($schedule_id)) && $schedule->is_enabled()) { + $schedule->run(); + } + backup_migrate_cleanup(); +} + /** * Get all the available backup schedules. */ function backup_migrate_get_schedules() { - static $schedules = NULL; + $schedules = &drupal_static('backup_migrate_get_schedules'); // Get the list of schedules and cache them locally. if ($schedules === NULL) { $schedules = backup_migrate_crud_get_items('schedule'); @@ -42,16 +100,18 @@ function backup_migrate_get_schedule($schedule_id) { * A schedule class for crud operations. */ class backup_migrate_schedule extends backup_migrate_item { - var $db_table = "backup_migrate_schedules"; - var $type_name = 'schedule'; - var $singular = 'schedule'; - var $plural = 'schedules'; - var $default_values = array(); + public $db_table = "backup_migrate_schedules"; + public $type_name = 'schedule'; + public $singular = 'schedule'; + public $plural = 'schedules'; + public $title_plural = 'Schedules'; + public $title_singular = 'Schedule'; + public $default_values = array(); /** * This function is not supposed to be called. It is just here to help the po extractor out. */ - function strings() { + public function strings() { // Help the pot extractor find these strings. t('Schedule'); t('Schedules'); @@ -62,25 +122,36 @@ class backup_migrate_schedule extends backup_migrate_item { /** * Get the default values for this item. */ - function get_default_values() { + public function get_default_values() { return array( - 'name' => t("Untitled Schedule"), - 'source_id' => 'db', - 'enabled' => 1, - 'keep' => 0, - 'period' => 60 * 60 * 24, - 'storage' => BACKUP_MIGRATE_STORAGE_NONE - ); + 'name' => t("Untitled Schedule"), + 'source_id' => 'db', + 'enabled' => 1, + 'keep' => BACKUP_MIGRATE_KEEP_ALL, + 'period' => 60 * 60 * 24, + 'storage' => BACKUP_MIGRATE_STORAGE_NONE, + 'cron' => BACKUP_MIGRATE_CRON_BUILTIN, + 'cron_schedule' => '0 4 * * *', + ); + } + + /** + * Return as an array of values. + */ + public function to_array() { + $out = parent::to_array(); + unset($out['last_run']); + return $out; } /** * Get the columns needed to list the type. - */ - function get_list_column_info() { + */ + public function get_list_column_info() { $out = parent::get_list_column_info(); $out = array( 'name' => array('title' => t('Name')), - 'destination_name' => array('title' => t('Destination'), 'html' => TRUE), + 'destination_name' => array('title' => t('Destinations'), 'html' => TRUE), 'profile_name' => array('title' => t('Profile'), 'html' => TRUE), 'frequency_description' => array('title' => t('Frequency')), 'keep_description' => array('title' => t('Keep')), @@ -90,15 +161,40 @@ class backup_migrate_schedule extends backup_migrate_item { return $out; } + /** + * Get the columns needed to list the type. + */ + public function get_settings_path() { + // Pull the schedules tab up a level to the top. + return BACKUP_MIGRATE_MENU_PATH . '/' . $this->type_name; + } + + /** + * Get the menu items for manipulating this type. + */ + public function get_menu_items() { + $items = parent::get_menu_items(); + $path = $this->get_settings_path(); + return $items; + } + /** * Get a row of data to be used in a list of items of this type. */ - function get_list_row() { - drupal_add_css(drupal_get_path('module', 'backup_migrate') .'/backup_migrate.css'); + public function get_list_row() { + drupal_add_css(drupal_get_path('module', 'backup_migrate') . '/backup_migrate.css'); $row = parent::get_list_row(); if (!$this->is_enabled()) { foreach ($row as $key => $field) { - $row[$key] = array('data' => $field, 'class' => 'schedule-list-disabled'); + if (!is_array($field)) { + $row[$key] = array('data' => $field, 'class' => 'schedule-list-disabled'); + } + elseif (isset($row[$key]['class'])) { + $row[$key]['class'] .= ' schedule-list-disabled'; + } + else { + $row[$key]['class'] = 'schedule-list-disabled'; + } } } return $row; @@ -107,7 +203,7 @@ class backup_migrate_schedule extends backup_migrate_item { /** * Is the schedule enabled and valid. */ - function is_enabled() { + public function is_enabled() { $destination = $this->get_destination(); $profile = $this->get_profile(); return (!empty($this->enabled) && !empty($destination) && !empty($profile)); @@ -116,66 +212,167 @@ class backup_migrate_schedule extends backup_migrate_item { /** * Get the destination object of the schedule. */ - function get_destination() { - backup_migrate_include('destinations'); - return backup_migrate_get_destination($this->get('destination_id')); + public function get_destination() { + $destinations = (array) $this->get_destinations(); + return reset($destinations); + } + + /** + * Get the destination object of the schedule. + */ + public function get_destination_ids() { + $out = array(); + foreach (array('destination_id', 'copy_destination_id') as $key) { + if ($id = $this->get($key)) { + $out[$key] = $id; + } + } + return $out; + } + + /** + * Get the destination object of the schedule. + */ + public function get_destinations() { + require_once dirname(__FILE__) . '/destinations.inc'; + + $out = array(); + foreach ($this->get_destination_ids() as $id) { + if ($dest = backup_migrate_get_destination($id)) { + $out[$id] = $dest; + } + } + return $out; + } + + /** + * Get the destination object of the schedule. + */ + public function get_destination_remote() { + require_once dirname(__FILE__) . '/destinations.inc'; + + return backup_migrate_get_destination($this->get('destination_remote_id')); + } + + /** + * Get the destination object of the schedule. + */ + public function get_destination_local() { + require_once dirname(__FILE__) . '/destinations.inc'; + + return backup_migrate_get_destination($this->get('destination_local_id')); } /** * Get the name of the destination. */ - function get_destination_name() { - if ($destination = $this->get_destination()) { - return check_plain($destination->get_name()); + public function get_destination_name() { + if ($destinations = $this->get_destinations()) { + $out = array(); + foreach ((array) $destinations as $destination) { + $out[] = check_plain($destination->get_name()); + } + return implode(', ', $out); } - return '
'. t("Missing") .'
'; + return '
' . t("Missing") . '
'; } /** * Get the destination of the schedule. */ - function get_profile() { - backup_migrate_include('profiles'); - return backup_migrate_get_profile($this->get('profile_id')); + public function get_profile() { + require_once dirname(__FILE__) . '/profiles.inc'; + + if ($settings = backup_migrate_get_profile($this->get('profile_id'))) { + $settings->file_info = empty($settings->file_info) ? array() : $settings->file_info; + $settings->file_info += array( + 'schedule_id' => $this->get_id(), + 'schedule_name' => $this->get('name'), + ); + } + return $settings; + } /** * Get the name of the source. */ - function get_profile_name() { + public function get_profile_name() { if ($profile = $this->get_profile()) { return check_plain($profile->get_name()); } - return '
'. t("Missing") .'
'; + return '
' . t("Missing") . '
'; } /** * Format a frequency in human-readable form. */ - function get_frequency_description() { + public function get_frequency_description() { $period = $this->get_frequency_period(); - $out = format_plural(($this->period / $period['seconds']), $period['singular'], $period['plural']); + $cron = $this->get('cron'); + if ($cron == BACKUP_MIGRATE_CRON_BUILTIN) { + $out = format_plural(($this->period / $period['seconds']), $period['singular'], $period['plural']); + } + elseif ($cron == BACKUP_MIGRATE_CRON_ELYSIA) { + $out = $this->get('cron_schedule'); + } + else { + $out = t('None'); + } return $out; } /** * Format the number to keep in human-readable form. */ - function get_keep_description() { - return !empty($this->keep) ? $this->keep : t('All'); + public function get_keep_description() { + return $this->generate_keep_description($this->keep); + } + + /** + * Format a number to keep in human readable from. + */ + public function generate_keep_description($keep, $terse = TRUE) { + if ($keep == BACKUP_MIGRATE_KEEP_ALL) { + return t('all backups'); + } + elseif ($keep == BACKUP_MIGRATE_SMART_DELETE) { + $keep_hourly = variable_get('backup_migrate_smart_keep_hourly', BACKUP_MIGRATE_SMART_KEEP_HOURLY); + $keep_daily = variable_get('backup_migrate_smart_keep_daily', BACKUP_MIGRATE_SMART_KEEP_DAILY); + $keep_weekly = variable_get('backup_migrate_smart_keep_weekly', BACKUP_MIGRATE_SMART_KEEP_WEEKLY); + if ($terse) { + return t('!hours hourly, !days daily, !weeks weekly backups', + array( + '!hours' => $keep_hourly == PHP_INT_MAX ? t('all') : $keep_hourly, + '!days' => $keep_daily == PHP_INT_MAX ? t('all') : $keep_daily, + '!weeks' => $keep_weekly == PHP_INT_MAX ? t('all') : $keep_weekly, + )); + } + else { + return t('hourly backups !hours, daily backups !days and weekly backups !weeks', + array( + '!hours' => $keep_hourly == PHP_INT_MAX ? t('forever') : format_plural($keep_hourly, 'for 1 hour', 'for the past @count hours'), + '!days' => $keep_daily == PHP_INT_MAX ? t('forever') : format_plural($keep_daily, 'for 1 day', 'for the past @count days'), + '!weeks' => $keep_weekly == PHP_INT_MAX ? t('forever') : format_plural($keep_weekly, 'for 1 week', 'for the past @count weeks'), + ) + ); + } + } + + return format_plural($keep, 'last 1 backup', 'last @count backups'); } /** * Format the enabled status in human-readable form. */ - function get_enabled_description() { + public function get_enabled_description() { return !empty($this->enabled) ? t('Enabled') : t('Disabled'); } /** * Format the enabled status in human-readable form. */ - function get_last_run_description() { + public function get_last_run_description() { $last_run = $this->get('last_run'); return !empty($last_run) ? format_date($last_run, 'small') : t('Never'); } @@ -183,29 +380,27 @@ class backup_migrate_schedule extends backup_migrate_item { /** * Get the number of excluded tables. */ - function get_exclude_tables_count() { + public function get_exclude_tables_count() { return count($this->exclude_tables) ? count($this->exclude_tables) : t("No tables excluded"); } /** * Get the number of excluded tables. */ - function get_nodata_tables_count() { + public function get_nodata_tables_count() { return count($this->nodata_tables) ? count($this->nodata_tables) : t("No data omitted"); } /** * Get the edit form. */ - function edit_form() { - $form = parent::edit_form(); - backup_migrate_include('destinations', 'profiles'); + public function edit_form() { + require_once dirname(__FILE__) . '/destinations.inc'; + require_once dirname(__FILE__) . '/sources.inc'; + require_once dirname(__FILE__) . '/profiles.inc'; + + $form = parent::edit_form(); - $form['enabled'] = array( - "#type" => "checkbox", - "#title" => t("Enabled"), - "#default_value" => $this->get('enabled'), - ); $form['name'] = array( "#type" => "textfield", "#title" => t("Schedule Name"), @@ -220,7 +415,7 @@ class backup_migrate_schedule extends backup_migrate_item { "#options" => _backup_migrate_get_profile_form_item_options(), "#default_value" => $this->get('profile_id'), ); - $form['profile_id']['#description'] = ' '. l(t("Create new profile"), BACKUP_MIGRATE_MENU_PATH . "/profile/add"); + $form['profile_id']['#description'] = ' ' . l(t('Create new profile'), BACKUP_MIGRATE_MENU_PATH . '/settings/profile/add'); if (!$form['profile_id']['#options']) { $form['profile_id']['#options'] = array('0' => t('-- None Available --')); } @@ -232,40 +427,138 @@ class backup_migrate_schedule extends backup_migrate_item { $default_period = $this->get_frequency_period(); $default_period_num = $this->get('period') / $default_period['seconds']; - $form['period'] = array( + $form['enabled'] = array( + '#type' => "checkbox", + '#title' => t('Enabled'), + '#default_value' => $this->get('enabled'), + ); + $form['cron_settings'] = array( + '#type' => 'backup_migrate_dependent', + '#dependencies' => array( + 'enabled' => TRUE, + ), + ); + + $cron = $this->get('cron'); + $form['cron_settings']['cron_builtin'] = array( + "#type" => "radio", + "#title" => t('Run using Drupal\'s cron'), + '#return_value' => BACKUP_MIGRATE_CRON_BUILTIN, + "#description" => t('Run this schedule when !cron runs.', array('!cron' => l(t('your cron task'), 'http://drupal.org/cron'))), + "#default_value" => $cron ? $cron : BACKUP_MIGRATE_CRON_BUILTIN, + '#parents' => array('cron'), + ); + + $form['cron_settings']['period_settings'] = array( + '#type' => 'backup_migrate_dependent', + '#dependencies' => array( + 'cron' => BACKUP_MIGRATE_CRON_BUILTIN, + ), + ); + $form['cron_settings']['period_settings']['period'] = array( "#type" => "item", "#title" => t("Backup every"), "#prefix" => '
', "#suffix" => '
', "#tree" => TRUE, + '#parents' => array('period'), ); - $form['period']['number'] = array( + $form['cron_settings']['period_settings']['period']['number'] = array( "#type" => "textfield", "#size" => 6, "#default_value" => $default_period_num, + '#parents' => array('period', 'number'), ); - $form['period']['type'] = array( + $form['cron_settings']['period_settings']['period']['type'] = array( "#type" => "select", "#options" => $period_options, "#default_value" => $default_period['type'], + '#parents' => array('period', 'type'), ); - $form['keep'] = array( + $form['cron_settings']['cron_elysia'] = array( + "#type" => "radio", + "#title" => t('Run using Elysia cron'), + '#return_value' => BACKUP_MIGRATE_CRON_ELYSIA, + "#description" => t('You can specify exactly when this schedule should run using !elysia.', array('!elysia' => l(t('the Elysia Cron module'), 'http://drupal.org/project/elysia_cron'))), + "#default_value" => $cron ? $cron : BACKUP_MIGRATE_CRON_BUILTIN, + '#parents' => array('cron'), + ); + if (!module_exists('elysia_cron') && !module_exists('ultimate_cron')) { + $form['cron_settings']['cron_elysia']['#disabled'] = TRUE; + $form['cron_settings']['cron_elysia']['#description'] .= ' ' . t('Install !elysia to enable this option.', array('!elysia' => l(t('Elysia Cron'), 'http://drupal.org/project/elysia_cron'))); + } + $form['cron_settings']['cron_schedule_settings'] = array( + '#type' => 'backup_migrate_dependent', + '#dependencies' => array( + 'cron' => BACKUP_MIGRATE_CRON_ELYSIA, + ), + ); + $form['cron_settings']['cron_schedule_settings']['cron_schedule'] = array( + "#type" => "textfield", + "#title" => t('Cron Schedule'), + '#length' => 10, + "#description" => t('Specify the frequency of the schedule using standard cron notation. For more information see the !elysiareadme.', array('!elysiareadme' => l(t('the Elysia Cron README'), 'http://drupalcode.org/project/elysia_cron.git/blob/refs/heads/7.x-1.x:/README.txt'))), + "#default_value" => $this->get('cron_schedule'), + '#parents' => array('cron_schedule'), + ); + + $form['cron_settings']['cron_none'] = array( + "#type" => "radio", + "#title" => t('Do not run automatically'), + '#return_value' => 'none', + "#description" => t('Do not run this schedule automatically. You can still run it using !drush.', array('!drush' => l(t('Drush'), 'http://drupal.org/project/drush'))), + "#default_value" => $cron ? $cron : BACKUP_MIGRATE_CRON_BUILTIN, + '#parents' => array('cron'), + ); + + $keep = $this->get('keep'); + $form['delete'] = array( + '#type' => 'checkbox', + '#default_value' => $keep != 0, + '#title' => t('Automatically delete old backups'), + ); + $form['delete_settings'] = array( + '#type' => 'backup_migrate_dependent', + '#dependencies' => array( + 'delete' => TRUE, + ), + ); + + $keep_hourly = variable_get('backup_migrate_smart_keep_hourly', BACKUP_MIGRATE_SMART_KEEP_HOURLY); + $keep_daily = variable_get('backup_migrate_smart_keep_daily', BACKUP_MIGRATE_SMART_KEEP_DAILY); + $keep_weekly = variable_get('backup_migrate_smart_keep_weekly', BACKUP_MIGRATE_SMART_KEEP_WEEKLY); + $form['delete_settings']['smartdelete'] = array( + "#type" => "radio", + "#title" => t('Smart Delete'), + '#return_value' => BACKUP_MIGRATE_SMART_DELETE, + "#description" => t('Keep !keep. Recommended', array('!keep' => $this->generate_keep_description(BACKUP_MIGRATE_SMART_DELETE, FALSE))), + "#default_value" => $keep ? $keep : BACKUP_MIGRATE_SMART_DELETE, + '#parents' => array('deletetype'), + ); + $form['delete_settings']['standarddelete'] = array( + "#type" => "radio", + "#title" => t('Simple Delete'), + '#return_value' => BACKUP_MIGRATE_STANDARD_DELETE, + "#description" => t("Keep a specified number of files deleting the oldest ones first."), + "#default_value" => $keep > 0 ? BACKUP_MIGRATE_STANDARD_DELETE : 0, + '#parents' => array('deletetype'), + ); + $form['delete_settings']['keep-settings'] = array( + '#type' => 'backup_migrate_dependent', + '#dependencies' => array( + 'deletetype' => BACKUP_MIGRATE_STANDARD_DELETE, + ), + ); + $form['delete_settings']['keep-settings']['keep'] = array( "#type" => "textfield", "#size" => 6, "#title" => t("Number of Backup files to keep"), - "#description" => t("The number of backup files to keep before deleting old ones. Use 0 to never delete backups. Other files in the destination directory will get deleted if you specify a limit."), - "#default_value" => $this->get('keep'), + "#description" => t("The number of backup files to keep before deleting old ones."), + "#default_value" => $keep > 0 ? $keep : BACKUP_MIGRATE_KEEP_DEFAULT, ); - $destination_options = _backup_migrate_get_destination_form_item_options('scheduled backup'); - $form['destination_id'] = array( - "#type" => "select", - "#title" => t("Destination"), - "#description" => t("Choose where the backup file will be saved. Backup files contain sensitive data, so be careful where you save them."), - "#options" => $destination_options, - "#default_value" => $this->get('destination_id'), - ); - $form['destination_id']['#description'] .= ' '. l(t("Create new destination"), BACKUP_MIGRATE_MENU_PATH . "/destination/add"); + + $form['destination'] = _backup_migrate_get_destination_pulldown('scheduled backup', $this->get('destination_id'), $this->get('copy_destination_id')); return $form; } @@ -273,12 +566,19 @@ class backup_migrate_schedule extends backup_migrate_item { /** * Submit the edit form. */ - function edit_form_validate($form, &$form_state) { + public function edit_form_validate($form, &$form_state) { if (!is_numeric($form_state['values']['period']['number']) || $form_state['values']['period']['number'] <= 0) { form_set_error('period][number', t('Backup period must be a number greater than 0.')); } - if (!is_numeric($form_state['values']['keep']) || $form_state['values']['keep'] < 0) { - form_set_error('keep', t('Number to keep must be an integer greater than or equal to 0.')); + + if (!$form_state['values']['delete']) { + $form_state['values']['keep'] = 0; + } + elseif ($form_state['values']['deletetype'] == BACKUP_MIGRATE_SMART_DELETE) { + $form_state['values']['keep'] = BACKUP_MIGRATE_SMART_DELETE; + } + elseif (!is_numeric($form_state['values']['keep']) || $form_state['values']['keep'] <= 0) { + form_set_error('keep', t('Number to keep must be a number greater than 0.')); } parent::edit_form_validate($form, $form_state); } @@ -286,7 +586,7 @@ class backup_migrate_schedule extends backup_migrate_item { /** * Submit the edit form. */ - function edit_form_submit($form, &$form_state) { + public function edit_form_submit($form, &$form_state) { $periods = $this->frequency_periods(); $period = $periods[$form_state['values']['period']['type']]; $form_state['values']['period'] = $form_state['values']['period']['number'] * $period['seconds']; @@ -294,9 +594,9 @@ class backup_migrate_schedule extends backup_migrate_item { } /** - * Get the period of the frequency (ie: seconds, minutes etc.) + * Get the period of the frequency (ie: seconds, minutes etc.). */ - function get_frequency_period() { + public function get_frequency_period() { foreach (array_reverse($this->frequency_periods()) as $period) { if ($period['seconds'] && ($this->period % $period['seconds']) === 0) { return $period; @@ -308,7 +608,7 @@ class backup_migrate_schedule extends backup_migrate_item { * Get a list of available backup periods. Only returns time periods which have a * (reasonably) consistent number of seconds (ie: no months). */ - function frequency_periods() { + public function frequency_periods() { return array( 'seconds' => array('type' => 'seconds', 'seconds' => 1, 'title' => t('Seconds'), 'singular' => t('Once a second'), 'plural' => t('Every @count seconds')), 'minutes' => array('type' => 'minutes', 'seconds' => 60, 'title' => t('Minutes'), 'singular' => t('Once a minute'), 'plural' => t('Every @count minutes')), @@ -321,83 +621,242 @@ class backup_migrate_schedule extends backup_migrate_item { /** * Get the message to send to the user when confirming the deletion of the item. */ - function delete_confirm_message() { - return t('Are you sure you want to delete the profile %name? Any schedules using this profile will be disabled.', array('%name' => $this->get('name'))); + public function delete_confirm_message() { + return t('Are you sure you want to delete the schedule %name? Backups made with this schedule will not be deleted.', array('%name' => $this->get('name'))); } /** * Perform the cron action. Run the backup if enough time has elapsed. */ - function cron() { + public function cron() { $now = time(); // Add a small negative buffer (1% of the entire period) to the time to account for slight difference in cron run length. - $wait_time = $this->period - ($this->period * variable_get('backup_migrate_schedule_buffer', 0.01)); + $wait_time = $this->period - ($this->period * variable_get('backup_migrate_schedule_buffer', BACKUP_MIGRATE_SCHEDULE_BUFFER)); - if ($this->is_enabled() && ($now - $this->get('last_run')) >= $wait_time) { - if ($settings = $this->get_profile()) { - $settings->destination_id = $this->destination_id; - $settings->source_id = $this->source_id; - backup_migrate_perform_backup($settings); - $this->update_last_run($now); - $this->remove_expired_backups(); - } - else { - backup_migrate_backup_fail("Schedule '%schedule' could not be run because requires a profile which is missing.", array('%schedule' => $schedule->get_name()), $settings); - } + $cron = $this->get('cron'); + if ($cron == BACKUP_MIGRATE_CRON_BUILTIN && $this->is_enabled() && ($now - $this->get('last_run')) >= $wait_time) { + $this->run(); + } + } + + /** + * Run the actual schedule. + */ + public function run() { + // Clear cached profile data as it could have been altered by a previous + // schedule run. + drupal_static_reset('backup_migrate_get_profiles'); + + if ($settings = $this->get_profile()) { + $settings->source_id = $this->get('source_id'); + $settings->destination_id = $this->get('destination_ids'); + + $this->update_last_run(time()); + backup_migrate_perform_backup($settings); + $this->remove_expired_backups(); + } + else { + backup_migrate_backup_fail("Schedule '%schedule' could not be run because requires a profile which is missing.", array('%schedule' => $schedule->get_name()), $settings); } } /** * Set the last run time of a schedule to the given timestamp, or now if none specified. */ - function update_last_run($timestamp = NULL) { + public function update_last_run($timestamp = NULL) { if ($timestamp === NULL) { $timestamp = time(); } variable_set('backup_migrate_schedule_last_run_' . $this->get('id'), $timestamp); } -/** + /** * Set the last run time of a schedule to the given timestamp, or now if none specified. */ - function get_last_run($timestamp = NULL) { + public function get_last_run() { return variable_get('backup_migrate_schedule_last_run_' . $this->get('id'), 0); } - /** * Remove older backups keeping only the number specified by the aministrator. */ - function remove_expired_backups() { - backup_migrate_include('destinations'); + public function remove_expired_backups() { + require_once dirname(__FILE__) . '/destinations.inc'; $num_to_keep = $this->keep; // If num to keep is not 0 (0 is infinity). - if ($num_to_keep && ($destination = $this->get_destination())) { - $i = 0; - if ($destination->op('delete') && $destination_files = $destination->list_files()) { - // Sort the files by modified time. - foreach ($destination_files as $file) { - if ($file->is_recognized_type() && $destination->can_delete_file($file->file_id())) { - $files[str_pad($file->info('filetime'), 10, "0", STR_PAD_LEFT) ."-". $i++] = $file; - } + foreach ((array) $this->get_destinations() as $destination) { + if ($destination && $destination->op('delete') && $destination_files = $destination->list_files()) { + if ($num_to_keep == BACKUP_MIGRATE_SMART_DELETE) { + $this->smart_delete_backups( + $destination, + $destination_files, + variable_get('backup_migrate_smart_keep_subhourly', BACKUP_MIGRATE_SMART_KEEP_SUBHOURLY), + variable_get('backup_migrate_smart_keep_hourly', BACKUP_MIGRATE_SMART_KEEP_HOURLY), + variable_get('backup_migrate_smart_keep_daily', BACKUP_MIGRATE_SMART_KEEP_DAILY), + variable_get('backup_migrate_smart_keep_weekly', BACKUP_MIGRATE_SMART_KEEP_WEEKLY) + ); } - - // If we are beyond our limit, remove as many as we need. - $num_files = count($files); - - if ($num_files > $num_to_keep) { - $num_to_delete = $num_files - $num_to_keep; - // Sort by date. - ksort($files); - // Delete from the start of the list (earliest). - for ($i = 0; $i < $num_to_delete; $i++) { - $file = array_shift($files); - $destination->delete_file($file->file_id()); - } + elseif ($num_to_keep != BACKUP_MIGRATE_KEEP_ALL) { + $this->delete_backups($destination, $destination_files, $num_to_keep); } } } } + + /** + * Remove older backups keeping only the number specified by the aministrator. + */ + public function delete_backups($destination, $files, $num_to_keep) { + require_once dirname(__FILE__) . '/destinations.inc'; + + $num_to_keep = $this->keep; + + // Sort the files by modified time. + $i = 0; + foreach ($files as $id => $file) { + if ($file->is_recognized_type()) { + $time = $file->info('filetime'); + $sorted[$id] = $time; + } + } + asort($sorted); + + // If we are beyond our limit, remove as many as we need. + $num_files = count($files); + + if ($num_files > $num_to_keep) { + $num_to_delete = $num_files - $num_to_keep; + // Delete from the start of the list (earliest). + foreach ($sorted as $id => $time) { + if (!$num_to_delete--) { + break; + } + $destination->delete_file($id); + } + } + } + + /** + * Delete files keeping the specified number of hourly, daily, weekly and monthly backups. + */ + public function smart_delete_backups($destination, $files, $keep_subhourly = 3600, $keep_hourly = 24, $keep_daily = 14, $keep_weekly = PHP_INT_MAX, $keep_monthly = PHP_INT_MAX) { + // Each period must be an exact multiple of the next smallest period. + $now = time(); + $periods = array( + 'subhourly' => array( + 'delta' => 1, + 'keep' => $keep_subhourly, + 'last_time' => 0, + 'files' => array(), + ), + 'hourly' => array( + 'delta' => 60 * 60, + 'keep' => $keep_hourly, + 'last_time' => 0, + 'files' => array(), + ), + 'daily' => array( + 'delta' => 60 * 60 * 24, + 'keep' => $keep_daily, + 'last_time' => 0, + 'files' => array(), + ), + 'weekly' => array( + 'delta' => 60 * 60 * 24 * 7, + 'keep' => $keep_weekly, + 'last_time' => 0, + 'files' => array(), + ), + /* + 'monthly' => array( + 'delta' => 60*60*24*7*4, + 'keep' => $keep_monthly, + 'last_time' => 0, + 'files' => array(), + ), + */ + ); + + $keep_files = $filetimes = $times = $groups = $sorted = $saved = array(); + foreach ($files as $id => $file) { + if ($file->is_recognized_type()) { + $time = $file->info('filetime'); + $sorted[$id] = $time; + } + } + // Sort files, oldest first. + asort($sorted); + + // Reset internal pointer and get the oldest file time. + $oldest_file_time = reset($sorted); + // Save the oldest file. + $keep_files[key($sorted)] = key($sorted); + + foreach ($periods as $i => $period) { + $period_keep_files = array(); + $time = $oldest_file_time; + + // Set time from which we start saving files. + $period_start_time = $now - (($period['keep'] + 1) * $period['delta']); + + // Increase time to within one period time span of the period start + // time. This keeps all the different period starts aligned. + if ($time < $period_start_time) { + $time += ((int) ceil(($period_start_time - $time) / $period['delta'])) * $period['delta']; + } + + $file_id = $this->find_nearest_file($sorted, $time); + + do { + $period_keep_files[$file_id] = $file_id; + $last_file_id = $file_id; + $time += $period['delta']; + $file_id = $this->find_nearest_file($sorted, $time); + } while ($time < $now); + + $keep_files = array_merge($keep_files, $period_keep_files); + } + + // Do the delete. + foreach ($files as $id => $file) { + if (!isset($keep_files[$id])) { + $destination->delete_file($file->file_id()); + } + } + } + + /** + * + */ + protected function find_nearest_file($files, $time) { + $last_file_id = NULL; + $last_file_time = NULL; + + foreach ($files as $id => $file_time) { + if ($file_time >= $time) { + if ($last_file_time == NULL) { + return $id; + } + if ($file_time == $time) { + return $id; + } + $time_to_prev = $time - $last_file_time; + $time_to_next = $file_time - $time; + if ($time_to_prev >= $time_to_next) { + return $id; + } + else { + return $last_file_id; + } + + // Shouldn't hit this but you never know. + break; + } + $last_file_id = $id; + $last_file_time = $file_time; + } + } + } diff --git a/sites/all/modules/contrib/admin/backup_migrate/includes/sources.archivesource.inc b/sites/all/modules/contrib/admin/backup_migrate/includes/sources.archivesource.inc new file mode 100644 index 0000000..e6c5f17 --- /dev/null +++ b/sites/all/modules/contrib/admin/backup_migrate/includes/sources.archivesource.inc @@ -0,0 +1,314 @@ + 'archive', + 'location' => '.', + 'name' => t('Entire Site (code, files & DB)'), + 'show_in_list' => FALSE, + )); + return $out; + } + + /** + * Returns a list of backup filetypes. + */ + public function file_types() { + return array( + "sitearchive" => array( + "extension" => "sitearchive.tar", + "filemime" => "application/x-tar", + "backup" => TRUE, + "restore" => FALSE, + ), + ); + } + + /** + * Gets the form for the settings for this destination. + */ + public function backup_settings_default() { + $out = parent::backup_settings_default(); + $excludes = explode("\n", $out['exclude_filepaths']); + foreach ($excludes as $i => $exclude) { + $excludes[$i] = 'public://' . $exclude; + } + $excludes[] = 'private://backup_migrate'; + $excludes[] = conf_path() . '/settings.php'; + $excludes[] = file_directory_temp(); + + return array( + 'exclude_filepaths' => implode("\n", $excludes), + ); + } + + /** + * Backup from this source. + */ + public function _backup_to_file_php($file, $settings) { + if ($this->check_libs()) { + $base_dir = $this->get_realpath(); + + $excluded_paths = empty($settings->filters['exclude_filepaths']) ? '' : $settings->filters['exclude_filepaths']; + $exclude = $this->get_excluded_paths($settings); + $files = $this->get_files_to_backup($this->get_realpath(), $settings, $exclude); + if ($files) { + $manifest = $this->generate_manifest(); + $db = $this->get_db(); + + $file->push_type('sitearchive'); + $gz = new Archive_Tar($file->filepath(), FALSE); + + $gz->addModify(array($manifest), $file->name . '/', dirname($manifest)); + $gz->addModify($files, $file->name . '/docroot', $base_dir); + $gz->addModify($db, $file->name . '/', dirname($db)); + + unlink($manifest); + rmdir(dirname($manifest)); + unlink($db); + rmdir(dirname($db)); + + return $file; + } + backup_migrate_backup_fail('No files available.', array(), $settings); + return FALSE; + } + return FALSE; + } + + /** + * Backup from this source. + */ + public function _backup_to_file_cli($file, $settings) { + if (!empty($settings->filters['use_cli']) && function_exists('backup_migrate_exec') && function_exists('escapeshellarg')) { + $excluded_paths = empty($settings->filters['exclude_filepaths']) ? '' : $settings->filters['exclude_filepaths']; + foreach ($this->get_excluded_paths($excluded_paths) as $path) { + $exclude[] = '--exclude=' . escapeshellarg($path); + } + $exclude = implode(' ', $exclude); + + // Create a symlink in a temp directory so we can rename the file in the + // archive. + $temp = backup_migrate_temp_directory(); + + $manifest = $this->generate_manifest(); + $db = $this->get_db(); + rename($db, $temp . '/database.sql'); + rename($manifest, $temp . '/MANIFEST.ini'); + + $file->push_type('sitearchive'); + $link = $temp . '/docroot'; + $input = realpath($this->get_location()); + backup_migrate_exec("ln -s %input %link; tar --dereference -C %temp -rf %output $exclude .", array( + '%output' => $file->filepath(), + '%input' => $input, + '%temp' => $temp, + '%link' => $link, + )); + + return $file; + } + return FALSE; + } + + /** + * Generates a manifest file. + */ + public function generate_manifest() { + $info = array( + 'Global' => array( + 'datestamp' => time(), + 'formatversion' => '2011-07-02', + 'generator' => 'Backup and Migrate (http://drupal.org/project/backup_migrate)', + 'generatorversion' => BACKUP_MIGRATE_VERSION, + ), + 'Site 0' => array( + 'version' => VERSION, + 'name' => variable_get('site_name', ''), + 'docroot' => 'docroot', + 'sitedir' => 'docroot/' . conf_path(), + 'database-file-default' => 'database.sql', + 'database-file-driver' => 'mysql', + ), + ); + if ($private = variable_get('file_private_path', FALSE)) { + $info['Site 0']['files-private'] = 'docroot/' . $private; + } + $info['Site 0']['files-public'] = 'docroot/' . variable_get('file_public_path', FALSE); + $ini = $this->_array_to_ini($info); + + $tmpdir = backup_migrate_temp_directory(); + $filepath = $tmpdir . '/MANIFEST.ini'; + file_put_contents($filepath, $ini); + return $filepath; + } + + /** + * Gets a database dump to add to the archive. + */ + public function get_db() { + require_once dirname(__FILE__) . '/destinations.inc'; + require_once dirname(__FILE__) . '/files.inc'; + require_once dirname(__FILE__) . '/filters.inc'; + require_once dirname(__FILE__) . '/profiles.inc'; + + $file = new backup_file(); + // Clone the default settings so we can make changes without them leaking + // out of this function. + $settings = clone _backup_migrate_profile_saved_default_profile(); + $settings->source_id = 'db'; + $settings->filters['compression'] = 'none'; + + // Execute the backup on the db with the default settings. + $file = backup_migrate_filters_backup($file, $settings); + + // Generate a tmp file with the correct final title (because ArchiveTar + // doesn't seem to allow renaming). + $tmpdir = backup_migrate_temp_directory(); + $filepath = $tmpdir . '/database.sql'; + rename($file->filepath(), $filepath); + + return $filepath; + } + + /** + * Restores to this source. + */ + public function _restore_from_file_php($file, &$settings) { + $success = FALSE; + if ($this->check_libs()) { + $from = $file->pop_type(); + $temp = backup_migrate_temp_directory(); + + $tar = new Archive_Tar($from->filepath()); + $tar->extractModify($temp, $file->name); + + // Parse the manifest. + $manifest = $this->read_manifest($temp); + + // Currently only the first site in the archive is supported. + $site = $manifest['Site 0']; + + $docroot = $temp . '/' . $site['docroot']; + $sqlfile = $temp . '/' . $site['database-file-default']; + $filepath = NULL; + if (isset($site['files-private'])) { + $filepath = $temp . '/' . $site['files-private']; + } + elseif (isset($site['files-public'])) { + $filepath = $temp . '/' . $site['files-public']; + } + + // Move the files from the temp directory. + if ($filepath && file_exists($filepath)) { + _backup_migrate_move_files($filepath, variable_get('file_public_path', conf_path() . '/files')); + } + else { + _backup_migrate_message('Files were not restored because the archive did not seem to contain a files directory or was in a format that Backup and Migrate couldn\'t read', array(), 'warning'); + } + + // Restore the sql db. + if ($sqlfile && file_exists($sqlfile)) { + $db_settings = clone $settings; + $db_settings->source_id = 'db'; + $file = new backup_file(array('filepath' => $sqlfile)); + $success = backup_migrate_filters_restore($file, $db_settings); + } + else { + _backup_migrate_message('The database was not restored because the archive did not seem to contain a database backup or was in a format that Backup and Migrate couldn\'t read', array(), 'warning'); + } + + if ($docroot) { + _backup_migrate_message('Backup and Migrate cannot restore the php code of the site for security reasons. You will have to copy the code to the server by hand if you wish to restore the full site.', array(), 'warning'); + } + + return $success && $file; + } + return FALSE; + } + + /** + * Restores to this source. + */ + public function _restore_from_file_cli($file, &$settings) { + // @todo implement the cli version of the restore. + return FALSE; + } + + /** + * Generates a manifest file. + */ + public function read_manifest($directory) { + // Assume some defaults if values ore the manifest is missing. + $defaults = array( + 'docroot' => 'docroot', + 'database-file-default' => 'database.sql', + 'database-file-driver' => 'mysql', + ); + + $out = $this->_ini_to_array($directory . '/MANIFEST.ini'); + + // Set the defaults. + $out['Site 0'] = isset($out['Site 0']) ? $out['Site 0'] : array(); + $out['Site 0'] += $defaults; + + return $out; + } + + /** + * Converts an associated array to an ini format string. + * + * Only allows 2 levels of depth to allow parse_ini_file to parse. + */ + public function _array_to_ini($sections) { + $content = ""; + foreach ($sections as $section => $data) { + $content .= '[' . $section . ']' . "\n"; + foreach ($data as $key => $val) { + $content .= $key . " = \"" . $val . "\"\n"; + } + $content .= "\n"; + } + return $content; + } + + /** + * Converts an associated array to an ini format string. + * + * Only allows 2 levels of depth to allow parse_ini_file to parse. + */ + public function _ini_to_array($path) { + return parse_ini_file($path, TRUE); + } + +} diff --git a/sites/all/modules/contrib/admin/backup_migrate/includes/sources.db.inc b/sites/all/modules/contrib/admin/backup_migrate/includes/sources.db.inc new file mode 100644 index 0000000..ee7b543 --- /dev/null +++ b/sites/all/modules/contrib/admin/backup_migrate/includes/sources.db.inc @@ -0,0 +1,330 @@ +set_source($this->get_id()); + + // Restore the file to the source database. + $file = backup_migrate_perform_restore($this->get_id(), $file, $settings); + + return $file; + } + + /** + * Destination configuration callback. + */ + public function edit_form() { + $form = parent::edit_form(); + $form['scheme']['#default_value'] = $this->default_scheme(); + $form['scheme']['#access'] = FALSE; + $form['path']['#title'] = t('Database name'); + $form['path']['#description'] = t('The name of the database. The database must exist, it will not be created for you.'); + $form['user']['#description'] = t('Enter the name of a user who has write access to the database.'); + return $form; + } + + /** + * Validate the configuration form. Make sure the db info is valid. + */ + public function edit_form_validate($form, &$form_state) { + if (!preg_match('/[a-zA-Z0-9_\$]+/', $form_state['values']['path'])) { + form_set_error('path', t('The database name is not valid.')); + } + parent::edit_form_validate($form, $form_state); + } + + /** + * Get the default settings for this object. + * + * @return array + * The default tables whose data can be ignored. These tables mostly + * contain info which can be easily reproducted (such as cache or search + * index) but also tables which can become quite bloated but are not + * necessarily extremely important to back up or migrate during development + * (such as access log and watchdog). + */ + public function backup_settings_default() { + $all_tables = $this->_get_table_names(); + + // Basic modules that should be excluded. + $basic = array( + // Default core tables. + 'accesslog', + 'sessions', + 'watchdog', + // Search module. + 'search_dataset', + 'search_index', + 'search_keywords_log', + 'search_total', + // Devel module. + 'devel_queries', + 'devel_times', + ); + + // Identify all cache tables. + $cache = array('cache'); + foreach ($all_tables as $table_name) { + if (strpos($table_name, 'cache_') === 0) { + $cache[] = $table_name; + } + } + + // Simpletest can create a lot of tables that do not need to be backed up, + // but all of them start with the string 'simpletest' so they can be easily + // excluded. + $simpletest = array(); + foreach ($all_tables as $table_name) { + if (strpos($table_name, 'simpletest') === 0) { + $simpletest[] = $table_name; + } + } + + return array( + 'nodata_tables' => drupal_map_assoc(array_merge($basic, $cache, module_invoke_all('devel_caches'))), + 'exclude_tables' => $simpletest, + 'utils_lock_tables' => FALSE, + ); + } + + /** + * Get the form for the backup settings for this destination. + */ + public function backup_settings_form($settings) { + $objects = $this->get_object_names(); + $form['#description'] = t("You may omit specific tables, or specific table data from the backup file. Only omit data that you know you will not need such as cache data, or tables from other applications. Excluding tables can break your Drupal install, so do not change these settings unless you know what you're doing."); + $form['exclude_tables'] = array( + "#type" => "select", + "#multiple" => TRUE, + "#title" => t("Exclude the following tables altogether"), + "#options" => $objects, + "#default_value" => $settings['exclude_tables'], + "#description" => t("The selected tables will not be added to the backup file."), + ); + $form['nodata_tables'] = array( + "#type" => "select", + "#multiple" => TRUE, + "#title" => t("Exclude the data from the following tables"), + "#options" => $objects, + "#default_value" => $settings['nodata_tables'], + "#description" => t("The selected tables will have their structure backed up but not their contents. This is useful for excluding cache data to reduce file size."), + ); + $form['utils_lock_tables'] = array( + '#type' => 'checkbox', + '#title' => t('Lock tables during backup'), + '#default_value' => !empty($settings['utils_lock_tables']) ? $settings['utils_lock_tables'] : NULL, + '#description' => t('This can help reduce data corruption, but will make your site unresponsive.'), + ); + return $form; + } + + /** + * Backup from this source. + */ + public function backup_to_file($file, $settings) { + $file->push_type($this->get_file_type_id()); + + // $this->lock_tables($settings); + // Switch to a different db if specified. + if (variable_get('backup_migrate_verbose')) { + _backup_migrate_message('Start peak memory usage: %mem', array('%mem' => backup_migrate_get_peak_memory_usage() . 'MB'), 'success'); + } + $success = $this->_backup_db_to_file($file, $settings); + if (variable_get('backup_migrate_verbose')) { + _backup_migrate_message('Finish peak memory usage: %mem', array('%mem' => backup_migrate_get_peak_memory_usage() . 'MB'), 'success'); + } + + // $this->unlock_tables($settings); + return $success ? $file : FALSE; + } + + /** + * Restore to this source. + */ + public function restore_from_file($file, &$settings) { + $num = 0; + $type = $this->get_file_type_id(); + // Open the file using the file wrapper. Check that the dump is of the right type (allow .sql for legacy reasons). + if ($file->type_id() !== $this->get_file_type_id() && $file->type_id() !== 'sql') { + _backup_migrate_message("Unable to restore from file %file because a %type file can't be restored to this database.", array("%file" => $file->filepath(), '%type' => $file->type_id()), 'error'); + } + else { + backup_migrate_filters_invoke_all('pre_restore', $file, $settings); + + // Restore the database. + $num = $this->_restore_db_from_file($file, $settings); + $settings->performed_action = $num ? t('%num SQL commands executed.', array('%num' => $num)) : ''; + + backup_migrate_filters_invoke_all('post_restore', $file, $settings, $num); + } + return $num; + } + + /** + * Get the db connection for the specified db. + */ + public function _get_db_connection() { + if (!$this->connection) { + $target = $key = ''; + $parts = explode(':', $this->get_id()); + // One of the predefined databases (set in settings.php) + if ($parts[0] == 'db') { + $key = empty($parts[1]) ? 'default' : $parts[1]; + $target = empty($parts[2]) ? 'default' : $parts[2]; + } + // Another db url. + else { + // If the url is specified build it into a connection info array. + if (!empty($this->dest_url)) { + $info = array( + 'driver' => empty($this->dest_url['scheme']) ? NULL : $this->dest_url['scheme'], + 'host' => empty($this->dest_url['host']) ? NULL : $this->dest_url['host'], + 'port' => empty($this->dest_url['port']) ? NULL : $this->dest_url['port'], + 'username' => empty($this->dest_url['user']) ? NULL : $this->dest_url['user'], + 'password' => empty($this->dest_url['pass']) ? NULL : $this->dest_url['pass'], + 'database' => empty($this->dest_url['path']) ? NULL : $this->dest_url['path'], + ); + $key = uniqid('backup_migrate_tmp_'); + $target = 'default'; + Database::addConnectionInfo($key, $target, $info); + } + // No database selected. Assume the default. + else { + $key = $target = 'default'; + } + } + if ($target && $key) { + $this->connection = Database::getConnection($target, $key); + } + } + return $this->connection; + } + + /** + * Backup the databases to a file. + */ + public function _backup_db_to_file($file, $settings) { + // Must be overridden. + } + + /** + * Backup the databases to a file. + */ + public function _restore_db_from_file($file, $settings) { + // Must be overridden. + } + + /** + * Get a list of objects in the database. + */ + public function get_object_names() { + // Must be overridden. + $out = $this->_get_table_names(); + if (method_exists($this, '_get_view_names')) { + $out += $this->_get_view_names(); + } + return $out; + } + + /** + * Get a list of tables in the database. + */ + public function get_table_names() { + // Must be overridden. + $out = $this->_get_table_names(); + return $out; + } + + /** + * Get a list of tables in the database. + */ + public function _get_table_names() { + // Must be overridden. + return array(); + } + + /** + * Lock the database in anticipation of a backup. + */ + public function lock_tables($settings) { + if ($settings->filters['utils_lock_tables']) { + $tables = array(); + foreach ($this->get_table_names() as $table) { + // There's no need to lock excluded or structure only tables because it doesn't matter if they change. + if (empty($settings->filters['exclude_tables']) || !in_array($table, (array) $settings->filters['exclude_tables'])) { + $tables[] = $table; + } + } + $this->_lock_tables($tables); + } + } + + /** + * Lock the list of given tables in the database. + */ + public function _lock_tables($tables) { + // Must be overridden. + } + + /** + * Unlock any tables that have been locked. + */ + public function unlock_tables($settings) { + if ($settings->filters['utils_lock_tables']) { + $this->_unlock_tables(); + } + } + + /** + * Unlock the list of given tables in the database. + */ + public function _unlock_tables($tables) { + // Must be overridden. + } + + /** + * Get the file type for to backup this destination to. + */ + public function get_file_type_id() { + return 'sql'; + } + + /** + * Get the version info for the given DB. + */ + public function _db_info() { + return array( + 'type' => FALSE, + 'version' => t('Unknown'), + ); + } + +} diff --git a/sites/all/modules/contrib/admin/backup_migrate/includes/sources.db.mysql.inc b/sites/all/modules/contrib/admin/backup_migrate/includes/sources.db.mysql.inc new file mode 100644 index 0000000..677e3a1 --- /dev/null +++ b/sites/all/modules/contrib/admin/backup_migrate/includes/sources.db.mysql.inc @@ -0,0 +1,593 @@ + array( + "extension" => "sql", + "filemime" => "text/x-sql", + "backup" => TRUE, + "restore" => TRUE, + ), + "mysql" => array( + "extension" => "mysql", + "filemime" => "text/x-sql", + "backup" => TRUE, + "restore" => TRUE, + ), + ); + } + + /** + * Return the scheme for this db type. + */ + public function default_scheme() { + return 'mysql'; + } + + /** + * Declare any mysql databases defined in the settings.php file as a possible source. + */ + public function sources() { + $out = array(); + global $databases; + foreach ((array) $databases as $db_key => $target) { + foreach ((array) $target as $tgt_key => $info) { + // Only mysql/mysqli supported by this source. + $key = $db_key . ':' . $tgt_key; + if ($info['driver'] === 'mysql') { + // Compile the database connection string. + $url = 'mysql://'; + $url .= urlencode($info['username']) . ':' . urlencode($info['password']); + $url .= '@'; + $url .= urlencode($info['host']); + if (!empty($info['port'])) { + $url .= ':' . $info['port']; + } + $url .= '/' . urlencode($info['database']); + + if ($source = backup_migrate_create_destination('mysql', array('url' => $url))) { + // Treat the default database differently because it is probably + // the only one available. + if ($key == 'default:default') { + $source->set_id('db'); + $source->set_name(t('Default Database')); + // Dissalow backing up to the default database because that's confusing and potentially dangerous. + $source->remove_op('scheduled backup'); + $source->remove_op('manual backup'); + } + else { + $source->set_id('db:' . $key); + $source->set_name($key . ": " . $source->get_display_location()); + } + $out[$source->get_id()] = $source; + } + } + } + } + return $out; + } + + /** + * Get the file type for to backup this source to. + */ + public function get_file_type_id() { + return 'mysql'; + } + + /** + * Backup the databases to a file. + * + * Returns a list of sql commands, one command per line. + * That makes it easier to import without loading the whole file into memory. + * The files are a little harder to read, but human-readability is not a priority. + */ + public function _backup_db_to_file($file, $settings) { + if (!empty($settings->filters['use_cli']) && $this->_backup_db_to_file_mysqldump($file, $settings)) { + return TRUE; + } + + $lines = 0; + $exclude = !empty($settings->filters['exclude_tables']) ? $settings->filters['exclude_tables'] : array(); + $nodata = !empty($settings->filters['nodata_tables']) ? $settings->filters['nodata_tables'] : array(); + if ($file->open(TRUE)) { + $file->write($this->_get_sql_file_header()); + $alltables = $this->_get_tables(); + $allviews = $this->_get_views(); + + foreach ($alltables as $table) { + if (_backup_migrate_check_timeout()) { + return FALSE; + } + if ($table['name'] && !isset($exclude[$table['name']])) { + $file->write($this->_get_table_structure_sql($table)); + $lines++; + if (!in_array($table['name'], $nodata)) { + $lines += $this->_dump_table_data_sql_to_file($file, $table); + } + } + } + foreach ($allviews as $view) { + if (_backup_migrate_check_timeout()) { + return FALSE; + } + if ($view['name'] && !isset($exclude[$view['name']])) { + $file->write($this->_get_view_create_sql($view)); + } + } + $file->write($this->_get_sql_file_footer()); + $file->close(); + return $lines; + } + else { + return FALSE; + } + } + + /** + * Backup the databases to a file using the mysqldump command. + */ + public function _backup_db_to_file_mysqldump($file, $settings) { + $success = FALSE; + $nodata_tables = array(); + $alltables = $this->_get_tables(); + + $command = 'mysqldump --result-file=%file --opt -Q --host=%host --port=%port --user=%user --password=%pass %db'; + $args = array( + '%file' => $file->filepath(), + '%host' => $this->dest_url['host'], + '%port' => isset($this->dest_url['port']) ? $this->dest_url['port'] : '3306', + '%user' => $this->dest_url['user'], + '%pass' => $this->dest_url['pass'], + '%db' => $this->dest_url['path'], + ); + + // Ignore the excluded and no-data tables. + $db = $this->dest_url['path']; + if (!empty($settings->filters['exclude_tables'])) { + foreach ((array) $settings->filters['exclude_tables'] as $table) { + if (isset($alltables[$table])) { + $command .= ' --ignore-table=' . $db . '.' . $table; + } + } + } + if (!empty($settings->filters['nodata_tables'])) { + foreach ((array) $settings->filters['nodata_tables'] as $table) { + if (isset($alltables[$table])) { + $nodata_tables[] = $table; + $command .= ' --ignore-table=' . $db . '.' . $table; + } + } + } + $success = backup_migrate_exec($command, $args); + + // Get the nodata tables. + if ($success && !empty($nodata_tables)) { + $tables = implode(' ', array_unique($nodata_tables)); + $command = "mysqldump --no-data --opt -Q --host=%host --port=%port --user=%user --password=%pass %db $tables >> %file"; + $success = backup_migrate_exec($command, $args); + } + return $success; + } + + /** + * Backup the databases to a file. + */ + public function _restore_db_from_file($file, $settings) { + $num = 0; + + if ($file->open() && $conn = $this->_get_db_connection()) { + // Optionally drop all existing tables. + if (!empty($settings->filters['utils_drop_all_tables'])) { + $all_tables = $this->_get_tables(); + $table_names = array_map('backup_migrate_array_name_value', $all_tables); + $table_list = join(', ', $table_names); + $stmt = $conn->prepare("DROP TABLE IF EXISTS $table_list;\n"); + $stmt->execute(); + } + + // Read one line at a time and run the query. + while ($line = $this->_read_sql_command_from_file($file)) { + if (_backup_migrate_check_timeout()) { + return FALSE; + } + if ($line) { + // Prepeare and exexute the statement instead of the api function to avoid substitution of '{' etc. + $stmt = $conn->prepare($line); + $stmt->execute(); + $num++; + } + } + // Close the file with fclose/gzclose. + $file->close(); + } + else { + drupal_set_message(t("Unable to open file %file to restore database", array("%file" => $file->filepath())), 'error'); + $num = FALSE; + } + return $num; + } + + /** + * Read a multiline sql command from a file. + * + * Supports the formatting created by mysqldump, but won't handle multiline comments. + */ + public function _read_sql_command_from_file($file) { + $out = ''; + while ($line = $file->read()) { + $first2 = substr($line, 0, 2); + $first3 = substr($line, 0, 3); + + // Ignore single line comments. This function doesn't support multiline comments or inline comments. + if ($first2 != '--' && ($first2 != '/*' || $first3 == '/*!')) { + $out .= ' ' . trim($line); + // If a line ends in ; or */ it is a sql command. + if (substr($out, strlen($out) - 1, 1) == ';') { + return trim($out); + } + } + } + return trim($out); + } + + /** + * Get a list of tables in the database. + */ + public function _get_table_names() { + if (empty(static::$tableNames)) { + static::$tableNames = $this->query("SHOW FULL TABLES WHERE Table_Type = 'BASE TABLE'") + ->fetchAllKeyed(0, 0); + } + + return static::$tableNames; + } + + /** + * Get a list of views in the database. + */ + public function _get_view_names() { + if (empty(static::$viewNames)) { + static::$viewNames = $this->query("SHOW FULL TABLES WHERE Table_Type = 'VIEW'") + ->fetchAllKeyed(0, 0); + } + + return static::$viewNames; + } + + /** + * Lock the list of given tables in the database. + */ + public function _lock_tables($tables) { + if ($tables) { + $tables_escaped = array(); + foreach ($tables as $table) { + $tables_escaped[] = '`' . db_escape_table($table) . '` WRITE'; + } + $this->query('LOCK TABLES ' . implode(', ', $tables_escaped)); + } + } + + /** + * Unlock all tables in the database. + */ + public function _unlock_tables($settings) { + $this->query('UNLOCK TABLES'); + } + + /** + * Get a list of table and view data in the db. + */ + protected function get_table_data() { + if (empty(static::$tableData)) { + $tables = $this->query('SHOW TABLE STATUS')->fetchAll(PDO::FETCH_ASSOC); + + foreach ($tables as $table) { + // Lowercase the keys because between Drupal 7.12 and 7.13/14 the + // default query behavior was changed. + // See: http://drupal.org/node/1171866 + $table = array_change_key_case($table); + static::$tableData[$table['name']] = $table; + } + } + + return static::$tableData; + } + + /** + * Get a list of tables in the db. + */ + public function _get_tables() { + $out = array(); + foreach ($this->get_table_data() as $table) { + if (!empty($table['engine'])) { + $out[$table['name']] = $table; + } + } + + return $out; + } + + /** + * Get a list of views in the db. + */ + public function _get_views() { + $out = array(); + foreach ($this->get_table_data() as $table) { + if (empty($table['engine'])) { + $out[$table['name']] = $table; + } + } + return $out; + } + + /** + * Get the sql for the structure of the given view. + */ + public function _get_view_create_sql($view) { + $out = ""; + // Switch SQL mode to get rid of "CREATE ALGORITHM..." what requires more permissions + troubles with the DEFINER user. + $sql_mode = $this->query("SELECT @@SESSION.sql_mode")->fetchField(); + $this->query("SET sql_mode = 'ANSI'"); + $result = $this->query("SHOW CREATE VIEW `" . $view['name'] . "`", array(), array('fetch' => PDO::FETCH_ASSOC)); + $this->query("SET SQL_mode = :mode", array(':mode' => $sql_mode)); + foreach ($result as $create) { + // Lowercase the keys because between Drupal 7.12 and 7.13/14 the default query behavior was changed. + // See: http://drupal.org/node/1171866 + $create = array_change_key_case($create); + $out .= "DROP VIEW IF EXISTS `" . $view['name'] . "`;\n"; + $out .= "SET sql_mode = 'ANSI';\n"; + $out .= strtr($create['create view'], "\n", " ") . ";\n"; + $out .= "SET sql_mode = '$sql_mode';\n"; + } + return $out; + } + + /** + * Get the sql for the structure of the given table. + */ + public function _get_table_structure_sql($table) { + $out = ""; + $result = $this->query("SHOW CREATE TABLE `" . $table['name'] . "`", array(), array('fetch' => PDO::FETCH_ASSOC)); + foreach ($result as $create) { + // Lowercase the keys because between Drupal 7.12 and 7.13/14 the default query behavior was changed. + // See: http://drupal.org/node/1171866 + $create = array_change_key_case($create); + $out .= "DROP TABLE IF EXISTS `" . $table['name'] . "`;\n"; + // Remove newlines and convert " to ` because PDO seems to convert those for some reason. + $out .= strtr($create['create table'], array("\n" => ' ', '"' => '`')); + if ($table['auto_increment']) { + $out .= " AUTO_INCREMENT=" . $table['auto_increment']; + } + $out .= ";\n"; + } + return $out; + } + + /** + * Get the sql to insert the data for a given table. + */ + public function _dump_table_data_sql_to_file($file, $table) { + $rows_per_query = variable_get('backup_migrate_data_rows_per_query', BACKUP_MIGRATE_DATA_ROWS_PER_QUERY); + $rows_per_line = variable_get('backup_migrate_data_rows_per_line', BACKUP_MIGRATE_DATA_ROWS_PER_LINE); + $bytes_per_line = variable_get('backup_migrate_data_bytes_per_line', BACKUP_MIGRATE_DATA_BYTES_PER_LINE); + + if (variable_get('backup_migrate_verbose')) { + _backup_migrate_message('Table: %table', array('%table' => $table['name']), 'success'); + } + + // Escape backslashes, PHP code, special chars. + $search = array('\\', "'", "\x00", "\x0a", "\x0d", "\x1a"); + $replace = array('\\\\', "''", '\0', '\n', '\r', '\Z'); + + $lines = 0; + $from = 0; + $args = array('fetch' => PDO::FETCH_ASSOC); + while ($data = $this->query("SELECT * FROM `" . $table['name'] . "`", array(), $args, $from, $rows_per_query)) { + if ($data->rowCount() == 0) { + break; + } + + $rows = $bytes = 0; + + $line = array(); + foreach ($data as $row) { + $from++; + + // DB Escape the values. + $items = array(); + foreach ($row as $key => $value) { + $items[] = is_null($value) ? "null" : "'" . str_replace($search, $replace, $value) . "'"; + } + + // If there is a row to be added. + if ($items) { + // Start a new line if we need to. + if ($rows == 0) { + $file->write("INSERT INTO `" . $table['name'] . "` VALUES "); + $bytes = $rows = 0; + } + // Otherwise add a comma to end the previous entry. + else { + $file->write(","); + } + + // Write the data itself. + $sql = implode(',', $items); + $file->write('(' . $sql . ')'); + $bytes += strlen($sql); + $rows++; + + // Finish the last line if we've added enough items. + if ($rows >= $rows_per_line || $bytes >= $bytes_per_line) { + $file->write(";\n"); + $lines++; + $bytes = $rows = 0; + } + } + } + + // Finish any unfinished insert statements. + if ($rows > 0) { + $file->write(";\n"); + $lines++; + } + } + + if (variable_get('backup_migrate_verbose')) { + _backup_migrate_message('Peak memory usage: %mem', array('%mem' => backup_migrate_get_peak_memory_usage() . 'MB'), 'success'); + } + + return $lines; + } + + /** + * Get the db connection for the specified db. + */ + public function _get_db_connection() { + if (!$this->connection) { + $this->connection = parent::_get_db_connection(); + // Set the sql mode because the default is ANSI,TRADITIONAL which is not aware of collation or storage engine. + $this->connection->exec("SET sql_mode=''"); + } + return $this->connection; + } + + /** + * Run a query on this source's database using Drupal's MySQL engine. + * + * @param string $query + * The query string. + * @param array $args + * Arguments for the query. + * @param array $options + * Options to pass to the query. + * @param int|null $from + * The starting point for the query; when passed will perform a queryRange() + * method instead of a regular query(). + * @param int|null $count + * The number of records to obtain from this query. Will be ignored if the + * $from argument is empty. + * + * @see DatabaseConnection_mysql::query() + * @see DatabaseConnection_mysql::queryRange() + */ + public function query($query, array $args = array(), array $options = array(), $from = NULL, $count = NULL) { + if ($conn = $this->_get_db_connection()) { + // If no $from is passed in, just do a basic query. + if (is_null($from)) { + return $conn->query($query, $args, $options); + } + // The $from variable was passed in, so do a ranged query. + else { + return $conn->queryRange($query, $from, $count, $args, $options); + } + } + } + + /** + * The header for the top of the sql dump file. These commands set the connection + * character encoding to help prevent encoding conversion issues. + */ + public function _get_sql_file_header() { + $info = $this->_db_info(); + + return "-- Backup and Migrate (Drupal) MySQL Dump +-- Backup and Migrate Version: " . BACKUP_MIGRATE_VERSION . " +-- http://drupal.org/project/backup_migrate +-- Drupal Version: " . VERSION . " +-- http://drupal.org/ +-- +-- Host: " . url('', array('absolute' => TRUE)) . " +-- Site Name: " . url('', array('absolute' => TRUE)) . " +-- Generation Time: " . format_date(time(), 'custom', 'r') . " +-- MySQL Version: " . $info['version'] . " + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE=NO_AUTO_VALUE_ON_ZERO */; + +SET SQL_MODE=\"NO_AUTO_VALUE_ON_ZERO\"; +SET NAMES utf8mb4; + +"; + } + + /** + * The footer of the sql dump file. + */ + public function _get_sql_file_footer() { + return " + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +"; + } + + /** + * Get the version info for the given DB. + */ + public function _db_info() { + $db = $this->_get_db_connection(); + return array( + 'type' => 'mysql', + 'version' => $db ? $db->version() : t('Unknown'), + ); + } + +} diff --git a/sites/all/modules/contrib/admin/backup_migrate/includes/sources.filesource.inc b/sites/all/modules/contrib/admin/backup_migrate/includes/sources.filesource.inc new file mode 100644 index 0000000..a6dfa64 --- /dev/null +++ b/sites/all/modules/contrib/admin/backup_migrate/includes/sources.filesource.inc @@ -0,0 +1,335 @@ + 'files', + 'location' => 'public://', + 'name' => t('Public Files Directory'), + 'show_in_list' => FALSE, + )); + if (variable_get('file_private_path', FALSE)) { + $out['files_private'] = backup_migrate_create_destination('filesource', array( + 'machine_name' => 'files', + 'location' => 'private://', + 'name' => t('Private Files Directory'), + 'show_in_list' => FALSE, + )); + } + return $out; + } + + /** + * Gets the form for the settings for the files destination. + */ + public function edit_form() { + $form = parent::edit_form(); + $form['location'] = array( + "#type" => "textfield", + "#title" => t("Directory path"), + "#default_value" => $this->get_location(), + "#required" => TRUE, + "#description" => t('Enter the path to the directory to back up. Use a relative path to pick a path relative to your Drupal root directory. The web server must be able to read from this path.'), + ); + return $form; + } + + /** + * Returns a list of backup filetypes. + */ + public function file_types() { + return array( + "tar" => array( + "extension" => "tar", + "filemime" => "application/x-tar", + "backup" => TRUE, + "restore" => TRUE, + ), + ); + } + + /** + * Gets the form for the settings for this destination. + * + * Return the default directories whose data can be ignored. These directories + * contain info which can be easily reproducted. Also exclude the backup and + * migrate folder to prevent exponential bloat. + */ + public function backup_settings_default() { + return array( + 'exclude_filepaths' => "backup_migrate\nstyles\ncss\njs\nctools\nless\nlanguages\nadvagg_css\nadvagg_js", + ); + } + + /** + * Get the form for the backup settings for this destination. + */ + public function backup_settings_form($settings) { + $form['exclude_filepaths'] = array( + "#type" => "textarea", + "#multiple" => TRUE, + "#title" => t("Exclude the following files or directories"), + "#default_value" => isset($settings['exclude_filepaths']) ? $settings['exclude_filepaths'] : '', + "#description" => t("A list of files or directories to be excluded from backups. Add one path per line relative to the directory being backed up."), + ); + return $form; + } + + /** + * Backup from this source. + */ + public function backup_to_file($file, $settings) { + if ($out = $this->_backup_to_file_cli($file, $settings)) { + return $out; + } + else { + return $this->_backup_to_file_php($file, $settings); + } + } + + /** + * Backup from this source. + */ + public function _backup_to_file_php($file, $settings) { + if ($this->check_libs()) { + $excluded = $this->get_excluded_paths($settings); + $files = $this->get_files_to_backup($this->get_realpath(), $settings, $excluded); + if ($files) { + $file->push_type('tar'); + $gz = new Archive_Tar($file->filepath(), FALSE); + $gz->addModify($files, '', $this->get_realpath()); + return $file; + } + backup_migrate_backup_fail('No files available.', array(), $settings); + return FALSE; + } + return FALSE; + } + + /** + * Backup from this source. + */ + public function _backup_to_file_cli($file, $settings) { + if (!empty($settings->filters['use_cli']) && function_exists('backup_migrate_exec') && function_exists('escapeshellarg')) { + $excluded = $this->get_excluded_paths($settings); + $exclude = array(); + foreach ($excluded as $path) { + $exclude[] = '--exclude=' . escapeshellarg($path); + } + $exclude = implode(' ', $exclude); + + // Create a symlink in a temp directory so we can rename the file in the + // archive. + $temp = backup_migrate_temp_directory(); + + $file->push_type('tar'); + backup_migrate_exec("tar --dereference -C %input -rf %output $exclude .", array( + '%output' => $file->filepath(), + '%input' => $this->get_realpath(), + '%temp' => $temp, + )); + return $file; + } + return FALSE; + } + + /** + * Restore to this source. + */ + public function restore_from_file($file, &$settings) { + if ($out = $this->_restore_from_file_cli($file, $settings)) { + return $out; + } + else { + return $this->_restore_from_file_php($file, $settings); + } + } + + /** + * Restore to this source. + */ + public function _restore_from_file_php($file, &$settings) { + if ($this->check_libs()) { + $from = $file->pop_type(); + $temp = backup_migrate_temp_directory(); + + $tar = new Archive_Tar($from->filepath()); + $tar->extractModify($temp, $file->name); + + // Older B&M Files format included a base 'files' directory. + if (file_exists($temp . '/files')) { + $temp = $temp . '/files'; + } + if (file_exists($temp . '/' . $file->name . '/files')) { + $temp = $temp . '/files'; + } + + // Move the files from the temp directory. + _backup_migrate_move_files($temp, $this->get_realpath()); + + return $file; + } + return FALSE; + } + + /** + * Restore to this source. + */ + public function _restore_from_file_cli($file, &$settings) { + if (!empty($settings->filters['use_cli']) && function_exists('backup_migrate_exec')) { + $temp = backup_migrate_temp_directory(); + backup_migrate_exec("tar -C %temp -xf %input", array('%input' => $file->filepath(), '%temp' => $temp)); + + // Older B&M Files format included a base 'files' directory. + if (file_exists($temp . '/files')) { + $temp = $temp . '/files'; + } + if (file_exists($temp . '/' . $file->name . '/files')) { + $temp = $temp . '/files'; + } + + // Move the files from the temp directory. + backup_migrate_exec("mv -rf %temp/* %output", array('%output' => $this->get_realpath(), '%temp' => $temp)); + return $file; + } + return FALSE; + } + + /** + * Gets a list of files to backup from the given set if dirs. + * + * Exclude any that match the array $exclude. + */ + public function get_files_to_backup($dir, $settings, $exclude = array()) { + $out = $errors = array(); + + if (!file_exists($dir)) { + backup_migrate_backup_fail('Directory %dir does not exist.', array('%dir' => $dir), $settings); + return FALSE; + } + if ($handle = @opendir($dir)) { + while (($file = readdir($handle)) !== FALSE) { + if ($file != '.' && $file != '..' && !in_array($file, $exclude)) { + $real = realpath($dir . '/' . $file); + // If the path is not excluded. + if (!in_array($real, $exclude)) { + if (is_dir($real)) { + $subdir = $this->get_files_to_backup($real, $settings, $exclude); + // If there was an error reading the subdirectory then abort the + // backup. + if ($subdir === FALSE) { + closedir($handle); + return FALSE; + } + // If the directory is empty, add an empty directory. + if (count($subdir) == 0) { + $out[] = $real; + } + $out = array_merge($out, $subdir); + } + else { + if (is_readable($real)) { + $out[] = $real; + } + else { + $errors[] = $dir . '/' . $file; + } + } + } + } + } + closedir($handle); + } + else { + backup_migrate_backup_fail('Could not open directory %dir', array('%dir' => $dir), $settings); + return FALSE; + } + + // Alert the user to any errors there might have been. + if ($errors) { + if (count($errors < 5)) { + $filesmsg = t('The following files: !files', array('!files' => theme('item_list', array('items' => $errors)))); + } + else { + $filesmsg = t('!count files', array('!count' => count($errors))); + } + + if (empty($settings->filters['ignore_errors'])) { + backup_migrate_backup_fail('The backup could not be completed because !files could not be read. If you want to skip unreadable files use the \'Ignore Errors\' setting under \'Advanced Options\' in \'Advanced Backup\' or in your schedule settings profile.', array('!files' => $filesmsg), 'error'); + $out = FALSE; + } + else { + backup_migrate_backup_fail('!files could not be read and were skipped', array('!files' => $filesmsg), 'error'); + } + } + + return $out; + } + + /** + * Breaks the excluded paths string into a usable list of paths. + */ + public function get_excluded_paths($settings) { + $base_dir = $this->get_realpath() . '/'; + $paths = empty($settings->filters['exclude_filepaths']) ? '' : $settings->filters['exclude_filepaths']; + $out = explode("\n", $paths); + foreach ($out as $key => $val) { + $path = trim($val, "/ \t\r\n"); + // If the path specified is a stream url or absolute path add the + // normalized version. + if ($real = drupal_realpath($path)) { + $out[$key] = $real; + } + // If the path is a relative path add it. + elseif ($real = drupal_realpath($base_dir . $path)) { + $out[$key] = $real; + } + // Otherwise add it as is even though it probably won't match any files. + else { + $out[$key] = $path; + } + } + return $out; + } + + /** + * Checks that the required libraries are installed. + */ + public function check_libs() { + $result = TRUE; + // Drupal 7 has Archive_Tar built in so there should be no need to include + // anything here. + return $result; + } + + /** + * Get the file location. + */ + public function get_realpath() { + return drupal_realpath($this->get_location()); + } + +} diff --git a/sites/all/modules/contrib/admin/backup_migrate/includes/sources.inc b/sites/all/modules/contrib/admin/backup_migrate/includes/sources.inc new file mode 100644 index 0000000..902c5b4 --- /dev/null +++ b/sites/all/modules/contrib/admin/backup_migrate/includes/sources.inc @@ -0,0 +1,274 @@ + array( + 'type_name' => t('Database'), + 'description' => t('Import the backup directly into another database. Database sources can also be used as a source to backup from.'), + 'file' => drupal_get_path('module', 'backup_migrate') . '/includes/sources.db.inc', + 'class' => 'backup_migrate_source_db', + 'can_create' => FALSE, + ), + 'mysql' => array( + 'type_name' => t('MySQL Database'), + 'description' => t('Import the backup directly into another MySQL database. Database sources can also be used as a source to backup from.'), + 'file' => drupal_get_path('module', 'backup_migrate') . '/includes/sources.db.mysql.inc', + 'class' => 'backup_migrate_source_db_mysql', + 'can_create' => TRUE, + ), + 'filesource' => array( + 'description' => t('A files directory which can be backed up from.'), + 'file' => drupal_get_path('module', 'backup_migrate') . '/includes/sources.filesource.inc', + 'class' => 'backup_migrate_destination_filesource', + 'type_name' => t('File Directory'), + 'can_create' => TRUE, + ), + 'archive' => array( + 'description' => t('Create an archive of your entire site.'), + 'file' => drupal_get_path('module', 'backup_migrate') . '/includes/sources.archivesource.inc', + 'class' => 'backup_migrate_files_destination_archivesource', + 'type_name' => t('Site Archive'), + 'can_create' => FALSE, + ), + ); + + return $out; +} + +/** + * Implements hook_backup_migrate_sources(). + * + * Get the built in backup sources and those in the db. + */ +function backup_migrate_backup_migrate_sources() { + require_once dirname(__FILE__) . '/filters.inc'; + + // Expose the configured databases as sources. + $out = backup_migrate_filters_invoke_all('sources'); + + return $out; +} + +/** + * Get the source options as a form element. + */ +function _backup_migrate_get_source_form($source_id = 'db') { + require_once dirname(__FILE__) . '/destinations.inc'; + + $form = array(); + $sources = _backup_migrate_get_source_pulldown($source_id); + if (count($sources['#options']) > 1) { + $form['source'] = array( + "#type" => "fieldset", + "#title" => t("Backup Source"), + "#collapsible" => TRUE, + "#collapsed" => FALSE, + "#tree" => FALSE, + ); + $sources['#description'] = t("Choose the database to backup. Any database destinations you have created and any databases specified in your settings.php can be backed up."); + + $form['source']['source_id'] = $sources; + } + else { + $form = array(); + $form['source']['source_id'] = array( + "#type" => "value", + "#value" => $source_id, + ); + } + return $form; +} + +/** + * Get pulldown to select existing source options. + */ +function _backup_migrate_get_source_pulldown($source_id = NULL) { + $sources = _backup_migrate_get_source_form_item_options(); + $form = array( + "#type" => "select", + "#title" => t("Backup Source"), + "#options" => _backup_migrate_get_source_form_item_options(), + "#default_value" => $source_id, + ); + return $form; +} + +/** + * Get the location options as an options array for a form item. + */ +function _backup_migrate_get_source_form_item_options() { + $out = array(); + foreach (backup_migrate_get_sources() as $key => $location) { + $out[$key] = $location->get_name(); + } + return $out; +} + +/** + * A base class for creating sources. + */ +class backup_migrate_source extends backup_migrate_location { + public $db_table = "backup_migrate_sources"; + public $type_name = 'source'; + public $singular = 'source'; + public $plural = 'sources'; + public $title_plural = 'Sources'; + public $title_singular = 'Source'; + + /** + * This function is not supposed to be called. + * + * It is just here to help out the po extractor. + */ + public function strings() { + // Help the pot extractor find these strings. + t('source'); + t('sources'); + t('Sources'); + t('Source'); + } + + /** + * Get the available location types. + */ + public function location_types() { + return backup_migrate_get_source_subtypes(); + } + +} + +/** + * A base class for creating sources. + */ +class backup_migrate_source_remote extends backup_migrate_source { + + /** + * The location is a URI so parse it and store the parts. + */ + public function get_location() { + return $this->url(FALSE); + } + + /** + * The location to display is the url without the password. + */ + public function get_display_location() { + return $this->url(TRUE); + } + + /** + * Returns the location with the password. + */ + public function set_location($location) { + $this->location = $location; + $this->set_url($location); + } + + /** + * Source configuration callback. + */ + public function edit_form() { + $form = parent::edit_form(); + $form['scheme'] = array( + "#type" => "textfield", + "#title" => t("Scheme"), + "#default_value" => @$this->dest_url['scheme'] ? $this->dest_url['scheme'] : '', + "#required" => TRUE, + "#weight" => 0, + ); + $form['host'] = array( + "#type" => "textfield", + "#title" => t("Host"), + "#default_value" => @$this->dest_url['host'] ? $this->dest_url['host'] : 'localhost', + "#required" => TRUE, + "#weight" => 10, + ); + $form['path'] = array( + "#type" => "textfield", + "#title" => t("Path"), + "#default_value" => @$this->dest_url['path'], + "#required" => TRUE, + "#weight" => 20, + ); + $form['user'] = array( + "#type" => "textfield", + "#title" => t("Username"), + "#default_value" => @$this->dest_url['user'], + "#required" => TRUE, + "#weight" => 30, + ); + $form['pass'] = array( + "#type" => "password", + "#title" => t("Password"), + "#default_value" => @$this->dest_url['pass'], + '#description' => '', + "#weight" => 40, + ); + if (@$this->dest_url['pass']) { + $form['old_password'] = array( + "#type" => "value", + "#value" => @$this->dest_url['pass'], + ); + $form['pass']["#description"] .= t('You do not need to enter a password unless you wish to change the currently saved password.'); + } + return $form; + } + + /** + * Submits the configuration form. + * + * Glue the url together and add the old password back if a new one was not + * specified. + */ + public function edit_form_submit($form, &$form_state) { + $form_state['values']['pass'] = $form_state['values']['pass'] ? $form_state['values']['pass'] : $form_state['values']['old_password']; + $form_state['values']['location'] = $this->glue_url($form_state['values'], FALSE); + parent::edit_form_submit($form, $form_state); + } + +} diff --git a/sites/all/modules/contrib/admin/backup_migrate/tests/BmTestBase.test b/sites/all/modules/contrib/admin/backup_migrate/tests/BmTestBase.test new file mode 100644 index 0000000..6d50b6f --- /dev/null +++ b/sites/all/modules/contrib/admin/backup_migrate/tests/BmTestBase.test @@ -0,0 +1,301 @@ + $password, + ); + user_save($account, $edit); + $account->pass_raw = $password; + + // Login. + $this->drupalLogin($account); + } + + /** + * {@inheritdoc} + */ + protected function verbose($message, $title = NULL) { + // Handle arrays, objects, etc. + if (!is_string($message)) { + $message = "
\n" . print_r($message, TRUE) . "\n
\n"; + } + + // Optional title to go before the output. + if (!empty($title)) { + $title = '

' . check_plain($title) . "

\n"; + } + + parent::verbose($title . $message); + } + + /** + * Confirm that a selector has the expected items. + */ + protected function assertSelectOptions($select_id, array $options, $message = '') { + $elements = $this + ->xpath('//select[@id=:id]//option', array( + ':id' => $select_id, + )); + $results = $this->assertEqual(count($elements), count($options), t('The same number of items were found as were requested')); + $this->verbose($elements); + + foreach ($options as $option) { + $elements = $this + ->xpath('//select[@id=:id]//option[@value=:option]', array( + ':id' => $select_id, + ':option' => $option, + )); + $this->verbose($elements); + $results *= $this->assertTrue(isset($elements[0]), $message ? $message : t('Option @option for field @id is present.', array( + '@option' => $option, + '@id' => $select_id, + )), t('Browser')); + } + + return $results; + } + + /** + * Confirm that a specific selector does not have items selected. + */ + protected function assertNoOptionsSelected($id, $message = '') { + $elements = $this + ->xpath('//select[@id=:id]//option[@selected="selected"]', array( + ':id' => $id, + )); + return $this + ->assertTrue(!isset($elements[0]), $message ? $message : t('Field @id does not have any selected items.', array( + '@id' => $id, + )), t('Browser')); + } + + /** + * Work out which compressor systems are supported by PHP. + * + * @return array + * The list of supported compressors. Will always include the item 'none'. + */ + protected function supportedCompressors() { + $items = array('none'); + + // Work out which systems are supported. + if (@function_exists("gzencode")) { + $items[] = 'gzip'; + } + if (@function_exists("bzcompress")) { + $items[] = 'bzip'; + } + if (class_exists('ZipArchive')) { + $items[] = 'zip'; + } + + return $items; + } + + /** + * Get a list of the files in a specific destination. + * + * @param string $destination_id + * The ID of the destination to check. Defaults to the manual file path. + * + * @return array + * The backup files found in the requested backup destination. + */ + protected function listBackupFiles($destination_id = 'manual') { + require_once dirname(__FILE__) . '/../includes/destinations.inc'; + + $items = array(); + + // Load the destination object. + $destination = backup_migrate_get_destination($destination_id); + if (!empty($destination)) { + $items = $destination->list_files(); + } + + return $items; + } + + /** + * Run a specific backup. + * + * @param string $destination_id + * The ID of the destination to check. Defaults to the manual file path. + * @param string $source_id + * The ID of the source to check. Defaults to the database. + */ + protected function runBackup($destination_id = 'manual', $source_id = 'db') { + $this->drupalGet(BACKUP_MIGRATE_MENU_PATH); + $this->assertResponse(200); + $edit = array( + 'destination_id' => $destination_id, + 'source_id' => $source_id, + ); + $this->drupalPost(NULL, $edit, 'Backup now'); + $this->assertResponse(200); + // Confirm the response is as expected. This is split up into separate + // pieces because it'd be more effort than is necessary right now to confirm + // what the exact filename is. + if ($source_id === 'db') { + $this->assertText('Default Database backed up successfully'); + } + else { + $this->assertText(' backed up successfully'); + } + $this->assertText('in destination'); + if ($source_id === 'db') { + $this->assertLink('download'); + $this->assertLink('restore'); + $this->assertLink('delete'); + } + } + + /** + * Delete all of the files in a specific backup destination. + * + * @param string $destination_id + * The ID of the destination to check. Defaults to the manual file path. + */ + protected function deleteBackups($destination_id = 'manual') { + $destination = backup_migrate_get_destination($destination_id); + $files = $this->listBackupFiles($destination_id); + if (!empty($files)) { + foreach ($files as $file_id => $file) { + $destination->delete_file($file_id); + } + } + } + + /** + * Work out whether a backup filename includes a timestamp. + * + * @param object $file + * The backup file to examine. + * + * @return mixed + * Returns 1 if found, 0 if not found, FALSE if an error occurs. + */ + protected function fileHasTimestamp($file) { + require_once dirname(__FILE__) . '/../includes/files.inc'; + + // Get the default filename, this is used later. + $default_filename = _backup_migrate_default_filename(); + $ext = implode('.', $file->ext); + $pattern = "/{$default_filename}-(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d)-(\d\d)-(\d\d).{$ext}/"; + + return preg_match($pattern, $file->file_info['filename']); + } + + /** + * Confirm that a backup filename includes a timestamp. + * + * @param object $file + * The backup file to examine. + * + * @return bool + * Indicates whether the file includes a timestamp. + */ + protected function assertFileTimestamp($file) { + return $this->assertTrue($this->fileHasTimestamp($file)); + } + + /** + * Confirm that a backup filename does not include a timestamp. + * + * @param object $file + * The backup file to examine. + * + * @return bool + * Indicates whether the file does not include a timestamp. + */ + protected function assertNoFileTimestamp($file) { + return !$this->assertFalse($this->fileHasTimestamp($file)); + } + + /** + * Get a profile. + * + * @param string $profile_id + * The name of the profile to load. Defaults to 'default'. + * + * @return object + * The profile object. + */ + protected function getProfile($profile_id = 'default') { + require_once dirname(__FILE__) . '/../includes/profiles.inc'; + + return backup_migrate_get_profile($profile_id); + } + + /** + * Confirm the most recently sent e-mail(s) contain the expected string. + * + * @param string $field_name + * Name of field or message property to assert: subject, body, id, etc. + * @param string $string + * String to search for. + * @param int $expected + * Number of times $string should occur in the field. + * @param int $email_depth + * Number of emails to search for string, starting with most recent. + * + * @return bool + * TRUE on pass, FALSE on fail. + * + * @see DrupalWebTestCase::assertMailString() + */ + protected function assertMailStringCount($field_name, $string, $expected, $email_depth) { + $mails = $this->drupalGetMails(); + $number_of_mails = count($mails); + $string_count = 0; + + for ($i = $number_of_mails - 1; $i >= $number_of_mails - $email_depth && $i >= 0; $i--) { + $mail = $mails[$i]; + + // Normalize whitespace, as it isn't eacy to know what the mail system + // might have done. Any run of whitespace becomes a single space. + $normalized_mail = preg_replace('/\\s+/', ' ', $mail[$field_name]); + $normalized_string = preg_replace('/\\s+/', ' ', $string); + $string_count += substr_count($normalized_mail, $normalized_string); + } + + $mail_count = min($number_of_mails, $email_depth); + $message = format_plural($mail_count, 'Text "@needle" was found @stringcount times in field "@field" of the most recent email message.', 'Text "@needle" was found @stringcount times in field "@field" of the @mailcount most recent email messages.', array( + '@needle' => $string, + '@stringcount' => $string_count, + '@field' => $field_name, + '@mailcount' => $mail_count, + )); + + return $this->assertTrue(($string_count === $expected), $message); + } + +} diff --git a/sites/all/modules/contrib/admin/backup_migrate/tests/BmTestBasics.test b/sites/all/modules/contrib/admin/backup_migrate/tests/BmTestBasics.test new file mode 100644 index 0000000..c222418 --- /dev/null +++ b/sites/all/modules/contrib/admin/backup_migrate/tests/BmTestBasics.test @@ -0,0 +1,276 @@ + 'Basic tests', + 'description' => 'Run through basic scenarios and functionality.', + 'group' => 'backup_migrate', + ); + } + + /** + * {@inheritdoc} + */ + public function setUp(array $modules = array()) { + parent::setUp($modules); + + // Log in as user 1, so that permissions are irrelevant. + $this->loginUser1(); + } + + /** + * Verify the main page has the expected form, then run a basic backup. + */ + public function testQuickBackup() { + // Ensure that the private file system is working correctly. + $this->drupalGet('admin/config/media/file-system'); + $this->assertResponse(200); + $edit = array(); + $this->drupalPost(NULL, $edit, 'Save configuration'); + $this->assertResponse(200); + $this->assertText('The configuration options have been saved.'); + + // Load the main B&M page. + $this->drupalGet(BACKUP_MIGRATE_MENU_PATH); + $this->assertResponse(200); + + // @todo Confirm each of the tabs are present. + // @todo Confirm each of the local tasks are present. + // Confirm the form has the expected fields. + $this->assertFieldByName('source_id'); + $this->assertFieldByName('destination_id'); + $this->assertFieldByName('profile_id'); + $this->assertFieldByName('copy'); + $this->assertFieldByName('copy_destination_id'); + $this->assertFieldByName('description_enabled'); + // This item should not have a value "selected", it just defaults to the + // first item being the active item. + $items = array('db', 'files', 'archive'); + $this->assertSelectOptions('edit-source-id', $items); + $this->assertNoOptionsSelected('edit-source-id'); + // This item should have a value "selected", not just the first item. Note: + // if the 'manual' backup option isn't available then the private directory + // path is not set correctly. + $items = array('manual', 'download'); + $this->assertSelectOptions('edit-destination-id', $items); + $this->assertOptionSelected('edit-destination-id', 'download'); + // This item should not have a value "selected", it just defaults to the + // first item being the active item. + $items = array('default'); + $this->assertSelectOptions('edit-profile-id', $items); + $this->assertNoOptionsSelected('edit-profile-id'); + // This item should not have a value "selected", it just defaults to the + // first item being the active item. + $items = array('manual', 'download'); + $this->assertSelectOptions('edit-copy-destination-id', $items); + $this->assertNoOptionsSelected('edit-copy-destination-id'); + + // Generate a backup and confirm it was created correctly. + $edit = array( + 'destination_id' => 'manual', + ); + $this->drupalPost(NULL, $edit, 'Backup now'); + $this->assertResponse(200); + // Confirm the response is as expected. This is split up into separate + // pieces because it'd be more effort than is necessary right now to confirm + // what the exact filename is. + $this->assertText('Default Database backed up successfully'); + $this->assertText('in destination Manual Backups Directory'); + $this->assertLink('download'); + $this->assertLink('restore'); + $this->assertLink('delete'); + + // Try requesting the backup file. + $xpath = $this + ->xpath('//a[normalize-space(text())=:label]', array( + ':label' => 'download', + )); + $this->verbose($xpath); + $this->assertTrue(isset($xpath[0]['href'])); + $this->assertNotNull($xpath[0]['href']); + // @todo This doesn't work on drupalci, so work out how to fix it. + // $this->drupalGet($xpath[0]['href']); + $this->assertResponse(200); + } + + /** + * Test the custom validators. + */ + public function testFieldValidators() { + // Need this file loaded for the custom validators. + module_load_include('advanced_settings.inc', 'backup_migrate'); + + $field = 'mock_field'; + + $element = array( + '#value' => 'a', + '#title' => '', + '#parents' => array($field), + ); + + // Test the memory limit validator. + $element['#title'] = 'Mock Field (Memory Limit)'; + $test_values = array( + // Value to be tested => validity. + // Special meaning: no limit. + '-1' => TRUE, + '50' => TRUE, + // 5 megabytes. + '5M' => TRUE, + // 500 kilobytes. + '.5M' => TRUE, + // 5 gigabytes. + '5G' => TRUE, + '.5G' => TRUE, + '1.5G' => TRUE, + '0' => TRUE, + 'a' => FALSE, + '-2' => FALSE, + // You cannot have half a byte. + '1.5' => FALSE, + '5T' => FALSE, + '5 G' => FALSE, + '.5.5G' => FALSE, + ); + foreach ($test_values as $value => $valid) { + $this->assertValidFieldValue('backup_migrate_memory_limit_validate', $element, $value, $valid, 'memory limit value'); + } + + // Test the positive non-zero integer validator. + $element['#title'] = 'Mock Field (Unsigned Integer)'; + $test_values = array( + '0' => TRUE, + '1' => TRUE, + '10000' => TRUE, + // NaN. + 'a' => FALSE, + // Not an integer. + '.5' => FALSE, + // Not positive. + '-1' => FALSE, + // Largest integer. + // @code + // PHP_INT_MAX => TRUE, + // @endcode + // Resolves to a decimal. + // @code + // (PHP_INT_MAX + 1) => FALSE, + // @endcode + ); + foreach ($test_values as $value => $valid) { + $this->assertValidFieldValue('backup_migrate_unsigned_integer_validate', $element, $value, $valid, 'zero or a positive integer'); + } + + // Test the validator for decimals ranging from 0 to 1. + $element['#title'] = 'Mock Field (Decimal between 0 and 1)'; + $test_values = array( + '0' => TRUE, + '1' => TRUE, + '0.1' => TRUE, + '.1' => TRUE, + '1.1' => FALSE, + '-1' => FALSE, + '-1.1' => FALSE, + '2' => FALSE, + 'a' => FALSE, + ); + foreach ($test_values as $value => $valid) { + $this->assertValidFieldValue('backup_migrate_fraction_validate', $element, $value, $valid, 'decimal between 0 and 1'); + } + } + + /** + * Asserts that the content of a field passes its associated validation. + * + * @param string $validator + * Drupal validator function callback. + * @param array $element + * Input for error setter. + * @param string $value + * Field value to be tested. + * @param bool $value_is_valid + * Expected outcome. + * @param string $type + * Description of value sub-type. + */ + protected function assertValidFieldValue($validator, array $element, $value, $value_is_valid, $type) { + $form = array(); + $form_state = array(); + $field = 'mock_field'; + $element['#value'] = $value; + + form_clear_error(); + call_user_func_array($validator, array($element, &$form_state, $form)); + $errors = form_get_errors(); + + if ($value_is_valid) { + $args = array( + '%value' => $value, + '%type' => $type, + ); + $msg = t('%value is a valid %type.', $args); + $result = $this->assertTrue(empty($errors[$field]), $msg); + } + else { + $args = array( + '%value' => $value, + '%type' => $type, + '%msg' => strip_tags($errors[$field]), + ); + $msg = t('%value is not a valid %type. Error message: "%msg".', $args); + $result = $this->assertFalse(empty($errors[$field]), $msg); + } + + return $result; + } + + /** + * Confirm backup_migrate_to_bytes() works. + */ + public function testBackupMigrateToBytes() { + // PHP manual: + // http://php.net/manual/en/ini.core.php#ini.memory-limit + // '-1' is a reserved value meaning 'no limit'. + $input = '-1'; + $expected = -1; + $this->assertEqual(backup_migrate_to_bytes($input), $expected); + + // PHP would halt before this, but let us test anyway. + $input = 'nonsense'; + $expected = 0; + $this->assertEqual(backup_migrate_to_bytes($input), $expected); + + // This is 123 bytes, but there is a bug in B&M that turns + // this into 12.5 megabytes. + $input = '123'; + $expected = 12582912; + $this->assertEqual(backup_migrate_to_bytes($input), $expected); + + // 45 megabytes. + $input = '45M'; + $expected = 47185920; + $this->assertEqual(backup_migrate_to_bytes($input), $expected); + + // 8 gigabytes. + $input = '8G'; + $expected = 8589934592; + $this->assertEqual(backup_migrate_to_bytes($input), $expected); + } + +} + +// @todo Test permissions. +// @todo Test admin forms. diff --git a/sites/all/modules/contrib/admin/backup_migrate/tests/BmTestCtools.test b/sites/all/modules/contrib/admin/backup_migrate/tests/BmTestCtools.test new file mode 100644 index 0000000..7bccdc1 --- /dev/null +++ b/sites/all/modules/contrib/admin/backup_migrate/tests/BmTestCtools.test @@ -0,0 +1,104 @@ + 'CTools tests', + 'description' => 'Test integration with the CTools module.', + 'group' => 'backup_migrate', + 'dependencies' => array('ctools'), + ); + } + + /** + * {@inheritdoc} + */ + public function setUp(array $modules = array()) { + $modules[] = 'bm_test'; + $modules[] = 'ctools'; + + parent::setUp($modules); + + // Log in as user 1, so that permissions are irrelevant. + $this->loginUser1(); + } + + /** + * Confirm automated settings exist. + */ + public function testSettingsPage() { + // Load the B&M Settings page. + $this->drupalGet(BACKUP_MIGRATE_MENU_PATH . '/settings'); + $this->assertResponse(200); + + // Does the mock schedule exist? + $this->assertText('Mock weekly database schedule'); + + // Does the mock source exist? + $this->assertText('Mock file directory'); + + // Does the mock destination exist? + $this->assertText('Mock e-mail destination'); + + // Load the B&M Schedule page. + $this->drupalGet(BACKUP_MIGRATE_MENU_PATH . '/schedule'); + $this->assertResponse(200); + + // Does the mock schedule exist? + $this->assertText('Mock weekly database schedule'); + + // Does the mock schedule contain the appropriate values? + $this->drupalGet(BACKUP_MIGRATE_MENU_PATH . '/schedule/edit/mock_db_weekly'); + $this->assertResponse(200); + + $fields = _bm_test_get_mock_schedule(); + + // @todo Deal with periods, but now I need sleep. + // See the get_frequency_period() method in includes/schedules.inc. + unset($fields['period']); + + // Test the destination selection with its own assertion. + $this->assertOptionSelected('edit-destination-id', $fields['destination_id']); + unset($fields['destination_id']); + + foreach ($fields as $key => $field) { + $id = 'edit-' . str_replace('_', '-', $key); + $this->assertFieldById($id, $field, 'Found field by id "' . $id . '" and value "' . $field . '".'); + } + + // Does the mock source contain the appropriate values? + $this->drupalGet(BACKUP_MIGRATE_MENU_PATH . '/settings/source/edit/mock_file_directory'); + $this->assertResponse(200); + + $fields = _bm_test_get_mock_source(); + unset($fields['subtype']); + foreach ($fields as $key => $field) { + $id = 'edit-' . str_replace('_', '-', $key); + $this->assertFieldById($id, $field, 'Found field by id "' . $id . '" and value "' . $field . '".'); + } + + // Does the mock destination contain the appropriate values? + $this->drupalGet(BACKUP_MIGRATE_MENU_PATH . '/settings/destination/edit/mock_email'); + $this->assertResponse(200); + + $fields = _bm_test_get_mock_destination(); + unset($fields['subtype']); + foreach ($fields as $key => $field) { + $id = 'edit-' . str_replace('_', '-', $key); + $this->assertFieldById($id, $field, 'Found field by id "' . $id . '" and value "' . $field . '".'); + } + } + +} diff --git a/sites/all/modules/contrib/admin/backup_migrate/tests/BmTestEmail.test b/sites/all/modules/contrib/admin/backup_migrate/tests/BmTestEmail.test new file mode 100644 index 0000000..9c2241e --- /dev/null +++ b/sites/all/modules/contrib/admin/backup_migrate/tests/BmTestEmail.test @@ -0,0 +1,207 @@ + 'Email handling', + 'description' => 'Confirm email handling works as intended.', + 'group' => 'backup_migrate', + ); + } + + /** + * {@inheritdoc} + */ + public function setUp(array $modules = array()) { + parent::setUp($modules); + + // Log in as user 1, so that permissions are irrelevant. + $this->loginUser1(); + } + + /** + * Confirm the whole email process. + */ + public function testAddEmailDestination() { + // See if we can add a destination. + // Load the email destination Add page. + $this->drupalGet(BACKUP_MIGRATE_MENU_PATH . '/settings/destination/add/email'); + $this->assertResponse(200); + + // Verify all of the expected fields exist. + $this->assertFieldByName('name'); + $this->assertFieldByName('name', 'Untitled Destination'); + $this->assertFieldByName('machine_name'); + $this->assertFieldByName('location'); + + // Set up test values. + $test_source = strtolower($this->randomName(16)); + $address = strtolower($this->randomName(10)) . '@example.com'; + $test_destination = strtolower($this->randomName(16)); + + // Submit e-mail destination form with invalid values. + $this->submitDestinationEmail('', $test_destination, $address); + $this->assertText(t('Destination name field is required.'), 'Name required.'); + + $this->submitDestinationEmail($this->randomString(16), '', $address); + $this->assertText(t('Machine-readable name field is required.'), 'Machine name required.'); + + $this->submitDestinationEmail($this->randomString(16), $test_destination, ''); + $this->assertText(t('Email Address field is required.'), 'E-mail required.'); + + // Step 1, set-up. + // Create a test destination. + $this->submitDestinationEmail($this->randomString(16), $test_destination, $address); + $this->assertText(t('Your destination was saved')); + + // Create a test source. + // @todo Test the source add form. + // @todo Maybe add a note somewhere about how we are using a small + // source on purpose, so as to not break the testing system. + $this->submitSourceFiles($this->randomString(16), $test_source, drupal_get_path('module', 'backup_migrate') . '/tests/files/'); + $this->assertText(t('Your source was saved')); + + // Run a manual back-up so we have a file to compare against. + $this->runBackup('manual', $test_source); + + $files = $this->listBackupFiles(); + $key = key($files); + + // Confirm the back-up file exists. + $this->assertNotEqual($key, NULL, 'A backup file was found.'); + + $file = $files[$key]; + + // Copied from destinations.email.inc to build a compatible parameter. + $attachment = new stdClass(); + $attachment->filename = $file->name; + $attachment->path = $file->path; + + // Step 2, e-mail generated through the Quick Backup form. + // Run a backup per e-mail. + $this->runBackup($test_destination, $test_source); + + // Confirm that an e-mail was sent. + $captured_emails = $this->drupalGetMails(array('id' => 'backup_migrate_destination_mail', 'to' => $address)); + $this->verboseEmail(); + $this->assertEqual(count($captured_emails), 1, 'A back-up was mailed.'); + + // Does the attachment occur in the captured e-mail? + // Note that the e-mail from Step 2 is tested against the back-up file from + // Step 1. + $file_data = fread(fopen($attachment->path, "r"), filesize($attachment->path)); + $encoded_file_data = chunk_split(base64_encode($file_data), 70, "\r\n"); + $this->assertMailString('body', $encoded_file_data, 1); + + // Step 3, e-mail generated through the main helper function. + // Make sure the new $address is never the same as the old one. + $address = strtolower($this->randomName(1 + strlen($address))) . '@example.com'; + + _backup_migrate_destination_email_mail_backup($attachment, $address); + + // Confirm that an e-mail is sent. + $captured_emails = $this->drupalGetMails(array('id' => 'backup_migrate_destination_mail', 'to' => $address)); + $this->verboseEmail(); + $this->assertEqual(count($captured_emails), 1, 'A back-up was mailed.'); + + // Check if the Subject field contains the filename. + $this->assertMailString('subject', $attachment->filename, 1); + + // Check if the body contains the filename. + $this->assertMailString('body', $attachment->filename, 1); + + // Step 4, (partial) e-mail generated through a mime_mail() object. + // Build an e-mail. + $test_mail = new mime_mail(); + $boundary = "b" . md5(uniqid(time())); + + // Make sure the new $address is never the same as the old one. + $address = strtolower($this->randomName(1 + strlen($address))) . '@example.com'; + + $attach = fread(fopen($attachment->path, "r"), filesize($attachment->path)); + $test_mail->add_attachment($attach, $attachment->filename, "application/octet-stream"); + + $message = $test_mail->build_multipart($boundary); + + $params = array( + 'body' => $message, + 'subject' => 'test', + 'headers' => array( + 'MIME-Version' => "1.0", + 'Content-Type' => "multipart/mixed; boundary=\"$boundary\"", + ), + ); + + drupal_mail('backup_migrate', 'destination_mail', $address, '', $params); + $captured_emails = $this->drupalGetMails(array('id' => 'backup_migrate_destination_mail', 'to' => $address)); + $this->verboseEmail(); + + // Confirm that an e-mail was sent. + $this->assertEqual(count($captured_emails), 1, 'A back-up was mailed.'); + + // Does the boundary occur in the captured e-mail? + $this->assertMailString('body', '--' . $boundary, 1); + + // How many boundaries? + $expected = count($test_mail->parts) + 1; + $this->assertMailStringCount('body', '--' . $boundary, $expected, 1); + + // Cleanup - purge all backups. + $this->deleteBackups(); + } + + /** + * Submits the destination form for E-mails. + * + * @param string $name + * The name of the destination. + * @param string $machine_name + * The machine name of the destination. + * @param string $mail + * The e-mail address of the destination. + */ + public function submitDestinationEmail($name, $machine_name, $mail) { + $this->drupalGet('admin/config/system/backup_migrate/settings/destination/add/email'); + $this->assertResponse(200); + $edit = array(); + $edit['name'] = $name; + $edit['machine_name'] = $machine_name; + $edit['location'] = $mail; + $this->drupalPost(NULL, $edit, t('Save destination')); + $this->assertResponse(200); + } + + /** + * Submits the source form for Files. + * + * @param string $name + * The name of the source. + * @param string $machine_name + * The machine name of the source. + * @param string $path + * The path of the source. + */ + public function submitSourceFiles($name, $machine_name, $path) { + $this->drupalGet('admin/config/system/backup_migrate/settings/source/add/filesource'); + $this->assertResponse(200); + $edit = array(); + $edit['name'] = $name; + $edit['machine_name'] = $machine_name; + $edit['location'] = $path; + $this->drupalPost(NULL, $edit, t('Save source')); + $this->assertResponse(200); + } + +} diff --git a/sites/all/modules/contrib/admin/backup_migrate/tests/BmTestProfiles.test b/sites/all/modules/contrib/admin/backup_migrate/tests/BmTestProfiles.test new file mode 100644 index 0000000..7a80697 --- /dev/null +++ b/sites/all/modules/contrib/admin/backup_migrate/tests/BmTestProfiles.test @@ -0,0 +1,170 @@ + 'Profiles tests', + 'description' => 'Run through basic scenarios and functionality.', + 'group' => 'backup_migrate', + ); + } + + /** + * {@inheritdoc} + */ + public function setUp(array $modules = array()) { + parent::setUp($modules); + + // Log in as user 1, so that permissions are irrelevant. + $this->loginUser1(); + } + + /** + * Verify the profile page has the expected functionality available. + */ + public function testProfilePage() { + // Load the main B&M page. + $this->drupalGet(BACKUP_MIGRATE_MENU_PATH . '/settings'); + $this->assertResponse(200); + + // Confirm the page has the expected settings details. + $this->assertText('Settings Profiles'); + $this->assertText('Default Settings'); + $this->assertLink('Create a new settings profile'); + } + + /** + * Confirm adding a new backup process works. + */ + public function testAddDefaultProfile() { + require_once dirname(__FILE__) . '/../includes/files.inc'; + require_once dirname(__FILE__) . '/../includes/profiles.inc'; + + // Load the main B&M page. + $this->drupalGet(BACKUP_MIGRATE_MENU_PATH . '/settings/profile/add'); + $this->assertResponse(200); + + $filename = _backup_migrate_default_filename(); + $defaults = _backup_migrate_profile_default_profile(); + + // Verify all of the expected fields exist. + $this->assertFieldByName('name'); + $this->assertFieldByName('name', 'Untitled Profile'); + $this->assertFieldByName('machine_name'); + $this->assertFieldByName('filename'); + $this->assertFieldByName('filename', $filename); + // @todo Confirm all of the expected options are present. + $this->assertFieldByName('append_timestamp'); + $this->assertFieldByName('timestamp_format'); + $this->assertFieldByName('timestamp_format', $defaults['timestamp_format']); + $this->assertFieldByName('filters[compression]'); + $items = $this->supportedCompressors(); + $this->assertSelectOptions('edit-filters-compression', $items); + $this->assertOptionSelected('edit-filters-compression', 'gzip'); + $this->assertFieldByName('filters[sources][db][exclude_tables][]'); + $this->assertFieldByName('filters[sources][db][nodata_tables][]'); + $this->assertFieldByName('filters[sources][db][utils_lock_tables]'); + $this->assertFieldByName('filters[sources][files][exclude_filepaths]'); + $this->assertFieldByName('filters[sources][archive][exclude_filepaths]'); + $this->assertFieldByName('filters[utils_site_offline]'); + $this->assertFieldByName('filters[utils_site_offline_message]'); + $this->assertFieldByName('filters[utils_description]'); + $this->assertFieldByName('filters[use_cli]'); + $this->assertFieldByName('filters[ignore_errors]'); + $this->assertFieldByName('filters[notify_success_enable]'); + $this->assertFieldByName('filters[notify_success_email]'); + $this->assertFieldByName('filters[notify_failure_enable]'); + $this->assertFieldByName('filters[notify_failure_email]'); + } + + /** + * Confirm the backup filename processes work as expected. + */ + public function testFilenameOptions() { + // Load the profile. This will be interacted with directly because otherwise + // the number of form fields will likely make it impossible to execute + // properly due to the max_input_vars setting defaulting to 1000. + $profile = $this->getProfile(); + + // Run a backup. + $this->runBackup(); + + // Confirm that there is only one file and it has a timestamp of some sort. + $files1 = $this->listBackupFiles(); + $this->verbose($files1); + $this->assertTrue(count($files1) === 1, 'One backup file was found.'); + $this->assertFileTimestamp(array_shift($files1)); + + // Run another backup. + $this->runBackup(); + + // Confirm that there are two backup files. + $files1b = $this->listBackupFiles(); + $this->verbose($files1b); + $this->assertTrue(count($files1b) === 2, 'Two backup files were found.'); + + // Cleanup before the next test - purge existing backups. + $this->deleteBackups(); + + // Change settings to "create separate backups". + $profile->append_timestamp = 0; + $profile->save(); + + // Run a backup. + $this->runBackup(); + + // Confirm that separate files are retained. + $files2 = $this->listBackupFiles(); + $this->verbose($files2); + $this->assertTrue(count($files2) === 1, 'One backup file was found.'); + $this->assertNoFileTimestamp(array_shift($files2)); + + // Run another backup. + $this->runBackup(); + + // Confirm that separate files are retained. + $files2b = $this->listBackupFiles(); + $this->verbose($files2b); + $this->assertTrue(count($files2b) === 2, 'Two backup files were found.'); + + // Cleanup before the next test - purge existing backups. + $this->deleteBackups(); + + // Change settings to "overwrite". + $profile->append_timestamp = 2; + $profile->save(); + + // Run a backup. + $this->runBackup(); + + // Confirm that a new file was created. + $files3 = $this->listBackupFiles(); + $this->verbose($files3); + $this->assertTrue(count($files3) === 1, 'One backup file was found.'); + $this->assertNoFileTimestamp(array_shift($files3)); + + // Run the backup again. + $this->runBackup(); + + // Confirm that a new file was not created. + $files3b = $this->listBackupFiles(); + $this->verbose($files3b); + $this->assertTrue(count($files3b) === 1, 'One backup file was found.'); + + // Cleanup - purge all backups. + $this->deleteBackups(); + } + +} diff --git a/sites/all/modules/contrib/admin/backup_migrate/tests/BmTestUpdate7310.test b/sites/all/modules/contrib/admin/backup_migrate/tests/BmTestUpdate7310.test new file mode 100644 index 0000000..5496045 --- /dev/null +++ b/sites/all/modules/contrib/admin/backup_migrate/tests/BmTestUpdate7310.test @@ -0,0 +1,141 @@ + 'Update 7310', + 'description' => 'Confirm update 7310 works as intended.', + 'group' => 'backup_migrate', + ); + } + + /** + * {@inheritdoc} + */ + public function setUp(array $modules = array()) { + parent::setUp($modules); + + // Log in as user 1, so that permissions are irrelevant. + $this->loginUser1(); + } + + /** + * Test update 7310. + */ + public function testUpdate7310() { + require_once dirname(__FILE__) . '/../includes/destinations.inc'; + require_once dirname(__FILE__) . '/../includes/schedules.inc'; + + $this->assertEqual(function_exists('backup_migrate_update_7310'), TRUE, 'Update 7310 exists.'); + + // First test what happens when there are no e-mail destinations. + // Execute the update function. + $result = backup_migrate_update_7310(); + + // We should be getting a notice as a result. + $message = "The following notice was displayed: "$result""; + $expected = 'No destinations were affected by this change.'; + $this->assertEqual($expected, $result, $message); + + // Next, add e-mail destinations and see what happens then. + // Create two mock e-mail destinations. + $address = strtolower($this->randomName(10)) . '@example.com'; + $email_destination_id = strtolower($this->randomName(16)); + $this->submitDestinationEmail('Mock e-mail destination', $email_destination_id, $address); + $address = strtolower($this->randomName(10)) . '@example.com'; + $email_destination_id = strtolower($this->randomName(16)); + $this->submitDestinationEmail('Mock e-mail destination', $email_destination_id, $address); + $this->assertText(t('Your destination was saved')); + + // Create mock schedules with different types of destinations. + $mock_schedule_1 = $this->randomName(10); + $mock_schedule_2 = $this->randomName(10); + $mock_schedule_3 = $this->randomName(10); + $this->submitSchedule($mock_schedule_1, 'test_1', 'scheduled', ''); + $this->assertText(t('Your schedule was saved')); + $this->submitSchedule($mock_schedule_2, 'test_2', $email_destination_id, ''); + $this->assertText(t('Your schedule was saved')); + $this->submitSchedule($mock_schedule_3, 'test_3', 'scheduled', $email_destination_id); + $this->assertText(t('Your schedule was saved')); + + $destinations = db_select('backup_migrate_destinations', 'bmd')->fields('bmd', array('machine_name'))->condition('subtype', 'email', '=')->execute()->fetchAllAssoc('machine_name', PDO::FETCH_ASSOC); + $destinations = array_keys($destinations); + + // Execute the update function. + $result = backup_migrate_update_7310(); + + // We should be getting a notice as a result. + $expected = htmlspecialchars("Schedules that back up to e-mail destinations have been disabled. Check that you are using the correct e-mail addresses, then re-enable manually. The following schedules have been disabled:
  • {$mock_schedule_2}
  • {$mock_schedule_3}
"); + $message = "The following notice was displayed: "$result"."; + $this->assertEqual($expected, $result, $message); + + $schedules_query = db_select('backup_migrate_schedules', 'bms')->fields('bms', array('machine_name', 'enabled')); + $schedules = $schedules_query->execute()->fetchAllAssoc('machine_name', PDO::FETCH_ASSOC); + + // Check that the correct values have changed. + $this->assertEqual($schedules['test_1']['enabled'], 1, 'The file back-up schedule remained enabled.'); + $this->assertEqual($schedules['test_2']['enabled'], 0, 'The e-mail back-up schedule was disabled.'); + $this->assertEqual($schedules['test_2']['enabled'], 0, 'The file back-up schedule with an e-mail copy was disabled.'); + } + + /** + * Submits the destination form for E-mails. + * + * @param string $name + * The name of the destination. + * @param string $machine_name + * The machine name of the destination. + * @param string $mail + * The e-mail address of the destination. + */ + public function submitDestinationEmail($name, $machine_name, $mail) { + $this->drupalGet('admin/config/system/backup_migrate/settings/destination/add/email'); + $this->assertResponse(200); + $edit = array(); + $edit['name'] = $name; + $edit['machine_name'] = $machine_name; + $edit['location'] = $mail; + $this->drupalPost(NULL, $edit, t('Save destination')); + $this->assertResponse(200); + } + + /** + * Submits the schedule form. + * + * @param string $name + * The name of the destination. + * @param string $machine_name + * The machine name of the destination. + * @param string $destination_id + * The destination ID to use. + * @param string $copy_destination_id + * The destination ID to use for an optional copy. + */ + public function submitSchedule($name, $machine_name, $destination_id, $copy_destination_id = NULL) { + $this->drupalGet('admin/config/system/backup_migrate/schedule/add'); + $this->assertResponse(200); + $edit = array(); + $edit['name'] = $name; + $edit['machine_name'] = $machine_name; + $edit['destination_id'] = $destination_id; + if (!empty($copy_destination_id)) { + $edit['copy'] = TRUE; + $edit['copy_destination_id'] = $copy_destination_id; + } + $this->drupalPost(NULL, $edit, t('Save schedule')); + $this->assertResponse(200); + } + +} diff --git a/sites/all/modules/contrib/admin/backup_migrate/tests/bm_test.info b/sites/all/modules/contrib/admin/backup_migrate/tests/bm_test.info new file mode 100644 index 0000000..0fc6938 --- /dev/null +++ b/sites/all/modules/contrib/admin/backup_migrate/tests/bm_test.info @@ -0,0 +1,13 @@ +name = "Backup and Migrate Mock Module" +description = "Mock module for testing the Backup and Migrate module." +package = Other +core = 7.x +hidden = TRUE +dependencies[] = simpletest +dependencies[] = backup_migrate + +; Information added by Drupal.org packaging script on 2020-07-15 +version = "7.x-3.9" +core = "7.x" +project = "backup_migrate" +datestamp = "1594807678" diff --git a/sites/all/modules/contrib/admin/backup_migrate/tests/bm_test.module b/sites/all/modules/contrib/admin/backup_migrate/tests/bm_test.module new file mode 100644 index 0000000..c051203 --- /dev/null +++ b/sites/all/modules/contrib/admin/backup_migrate/tests/bm_test.module @@ -0,0 +1,121 @@ + "1"); + } +} + +/** + * Implements hook_exportables_backup_migrate_schedules(). + */ +function bm_test_exportables_backup_migrate_schedules() { + $export = array(); + $item = new BmTestItem(); + $mock_record = _bm_test_get_mock_schedule(); + foreach ($mock_record as $key => $field) { + $item->$key = $field; + } + $export['mock_db_weekly'] = $item; + return $export; +} + +/** + * Helper function to create a mock schedule. + */ +function _bm_test_get_mock_schedule() { + return array( + 'machine_name' => 'mock_db_weekly', + 'name' => 'Mock weekly database schedule', + 'source_id' => 'db', + 'destination_id' => 'scheduled', + 'copy_destination_id' => '', + 'profile_id' => 'default', + 'keep' => 4, + 'period' => 604800, + 'enabled' => TRUE, + 'cron' => 'builtin', + 'cron_schedule' => '0 4 * * *', + ); +} + +/** + * Implements hook_exportables_backup_migrate_sources(). + */ +function bm_test_exportables_backup_migrate_sources() { + $export = array(); + $item = new BmTestItem(); + $mock_record = _bm_test_get_mock_source(); + foreach ($mock_record as $key => $field) { + $item->$key = $field; + } + $export['mock_file_directory'] = $item; + return $export; +} + +/** + * Helper function to create a mock source. + */ +function _bm_test_get_mock_source() { + return array( + 'machine_name' => 'mock_file_directory', + 'name' => 'Mock file directory', + 'location' => '/dev/null', + 'subtype' => 'filesource', + ); +} + +/** + * Implements hook_exportables_backup_migrate_destinations(). + */ +function bm_test_exportables_backup_migrate_destinations() { + $export = array(); + $item = new BmTestItem(); + $mock_record = _bm_test_get_mock_destination(); + foreach ($mock_record as $key => $field) { + $item->$key = $field; + } + $export['mock_email'] = $item; + return $export; +} + +/** + * Helper function to create a mock destination. + */ +function _bm_test_get_mock_destination() { + return array( + 'machine_name' => 'mock_email', + 'name' => 'Mock e-mail destination', + 'location' => 'test@example.com', + 'subtype' => 'email', + ); +} diff --git a/sites/all/modules/contrib/admin/backup_migrate/tests/files/test.txt b/sites/all/modules/contrib/admin/backup_migrate/tests/files/test.txt new file mode 100644 index 0000000..8c01d18 --- /dev/null +++ b/sites/all/modules/contrib/admin/backup_migrate/tests/files/test.txt @@ -0,0 +1,7 @@ +Nay, Traveller! rest. This lonely yew-tree stands +Far from all human dwelling: what if here +No sparkling rivulet spread the verdant herb; +What if these barren boughs the bee not loves; +Yet, if the wind breathe soft, the curling waves, +That break against the shore, shall lull thy mind +By one soft impulse saved from vacancy. diff --git a/sites/all/modules/contrib/admin/google_analytics/README.txt b/sites/all/modules/contrib/admin/google_analytics/README.txt index 1628cf7..8ef9818 100644 --- a/sites/all/modules/contrib/admin/google_analytics/README.txt +++ b/sites/all/modules/contrib/admin/google_analytics/README.txt @@ -1,6 +1,6 @@ Module: Google Analytics -Author: Alexander Hass +Author: Alexander Hass Description @@ -12,12 +12,20 @@ Requirements * Google Analytics user account - Installation ============ -* Copy the 'googleanalytics' module directory in to your Drupal +Copy the 'googleanalytics' module directory in to your Drupal sites/all/modules directory as usual. +Upgrading from 6.x-3.x and 7.x-1.x +================================== +If you upgrade from 6.x-3.x and 7.x-1.x (ga.js) to 7.x-2.x (analytics.js) you +should verify if you used custom variables. Write down your settings or make a +screenshot. You need to re-configure the settings to use custom dimensions or +metrics. There is no automatic upgrade path for custom variables feature. All +other module settings are upgraded automatically. + +See https://support.google.com/analytics/answer/2795983?hl=en for more details. Usage ===== @@ -27,12 +35,8 @@ All pages will now have the required JavaScript added to the HTML footer can confirm this by viewing the page source from your browser. -New approach to page tracking in 5.x-1.5 and 6.x-1.1 -==================================================== -With 5.x-1.5 and 6.x-1.1 there are new settings on the settings page at -admin/config/system/googleanalytics. The "Page specific tracking" area now -comes with an interface that copies Drupal's block visibility settings. - +Page specific tracking +====================== The default is set to "Add to every page except the listed pages". By default the following pages are listed for exclusion: @@ -44,33 +48,57 @@ node/*/* user/*/* These defaults are changeable by the website administrator or any other -user with 'administer google analytics' permission. +user with 'Administer Google Analytics' permission. -Like the blocks visibility settings in Drupal core, there is now a -choice for "Add if the following PHP code returns TRUE." Sample PHP snippets -that can be used in this textarea can be found on the handbook page -"Overview-approach to block visibility" at http://drupal.org/node/64135. +Like the blocks visibility settings in Drupal core, there is a choice for +"Add if the following PHP code returns TRUE." Sample PHP snippets that can be +used in this textarea can be found on the handbook page "Overview-approach to +block visibility" at https://drupal.org/node/64135. -Custom variables -================= -One example for custom variables tracking is the "User roles" tracking. Enter -the below configuration data into the custom variables settings form under -admin/config/system/googleanalytics. +Custom dimensions and metrics +============================= +One example for custom dimensions tracking is the "User roles" tracking. -Slot: 1 -Name: User roles -Value: [current-user:role-names] -Scope: Visitor +1. In the Google Analytics (https://marketingplatform.google.com/about/analytics/) + Management Interface you need to setup Dimension #1 with name + e.g. "User roles". This step is required. Do not miss it, please. -More details about Custom variables can be found in the Google API documentation at -http://code.google.com/intl/en/apis/analytics/docs/tracking/gaTrackingCustomVariables.html +2. Enter the below configuration data into the Drupal custom dimensions settings + form under admin/config/system/googleanalytics. You can also choose another + index, but keep it always in sync with the index used in step #1. + + Index: 1 + Value: [current-user:role-names] + +More details about custom dimensions and metrics can be found in the Google API +documentation at https://developers.google.com/analytics/devguides/collection/analyticsjs/custom-dims-mets Advanced Settings ================= You can include additional JavaScript snippets in the custom javascript code textarea. These can be found on the official Google Analytics pages -and a few examples at http://drupal.org/node/248699. Support is not +and a few examples at https://drupal.org/node/248699. Support is not provided for any customisations you include. -To speed up page loading you may also cache the Analytics ga.js +To speed up page loading you may also cache the Google Analytics "analytics.js" file locally. + +Manual JS debugging +=================== +For manual debugging of the JS code you are able to create a test node. This +is the example HTML code for this test node. You need to enable debugging mode +in your Drupal configuration of Google Analytics settings to see verbose +messages in your browsers JS console. + +Title: Google Analytics test page + +Body: + + +Text format: Full HTML diff --git a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.admin.inc b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.admin.inc index 40d5cad..2d8afa3 100644 --- a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.admin.inc +++ b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.admin.inc @@ -18,10 +18,17 @@ function googleanalytics_admin_settings_form($form_state) { '#title' => t('Web Property ID'), '#type' => 'textfield', '#default_value' => variable_get('googleanalytics_account', 'UA-'), - '#size' => 15, + '#size' => 20, '#maxlength' => 20, '#required' => TRUE, - '#description' => t('This ID is unique to each site you want to track separately, and is in the form of UA-xxxxxxx-yy. To get a Web Property ID, register your site with Google Analytics, or if you already have registered your site, go to your Google Analytics Settings page to see the ID next to every site profile. Find more information in the documentation.', array('@analytics' => 'http://www.google.com/analytics/', '@webpropertyid' => url('https://developers.google.com/analytics/resources/concepts/gaConceptsAccounts', array('fragment' => 'webProperty')))), + '#description' => t('This ID is unique to each site you want to track separately, and is in the form of UA-xxxxxxx-yy. To get a Web Property ID, register your site with Google Analytics, or if you already have registered your site, go to your Google Analytics Settings page to see the ID next to every site profile. Find more information in the documentation.', array('@analytics' => 'https://marketingplatform.google.com/about/analytics/', '@webpropertyid' => url('https://developers.google.com/analytics/resources/concepts/gaConceptsAccounts', array('fragment' => 'webProperty')))), + ); + + $form['account']['googleanalytics_premium'] = array( + '#type' => 'checkbox', + '#title' => t('Premium account'), + '#description' => t('If you are a Google Analytics Premium customer, you can use up to 200 instead of 20 custom dimensions and metrics.'), + '#default_value' => variable_get('googleanalytics_premium', FALSE), ); // Visibility settings. @@ -80,7 +87,15 @@ function googleanalytics_admin_settings_form($form_state) { '#title' => t('List of top-level domains'), '#type' => 'textarea', '#default_value' => variable_get('googleanalytics_cross_domains', ''), - '#description' => t('If you selected "Multiple top-level domains" above, enter all related top-level domains. Add one domain per line. By default, the data in your reports only includes the path and name of the page, and not the domain name. For more information see section Show separate domain names in Tracking Multiple Domains.', array('@url' => url('http://support.google.com/analytics/bin/answer.py', array('query' => array('answer' => '1034342'))))), + '#description' => t('If you selected "Multiple top-level domains" above, enter all related top-level domains. Add one domain per line. By default, the data in your reports only includes the path and name of the page, and not the domain name. For more information see section Show separate domain names in Tracking Multiple Domains.', array('@url' => 'https://support.google.com/analytics/answer/1034342')), + '#states' => array( + 'enabled' => array( + ':input[name="googleanalytics_domain_mode"]' => array('value' => '2'), + ), + 'required' => array( + ':input[name="googleanalytics_domain_mode"]' => array('value' => '2'), + ), + ), ); // Page specific visibility configurations. @@ -97,8 +112,8 @@ function googleanalytics_admin_settings_form($form_state) { if ($visibility == 2 && !$php_access) { $form['tracking']['page_vis_settings'] = array(); - $form['tracking']['page_vis_settings']['visibility'] = array('#type' => 'value', '#value' => 2); - $form['tracking']['page_vis_settings']['pages'] = array('#type' => 'value', '#value' => $pages); + $form['tracking']['page_vis_settings']['googleanalytics_visibility_pages'] = array('#type' => 'value', '#value' => 2); + $form['tracking']['page_vis_settings']['googleanalytics_pages'] = array('#type' => 'value', '#value' => $pages); } else { $options = array( @@ -161,7 +176,7 @@ function googleanalytics_admin_settings_form($form_state) { '#type' => 'fieldset', '#title' => t('Users'), ); - $t_permission = array('%permission' => t('opt-in or out of tracking')); + $t_permission = array('%permission' => t('Opt-in or out of tracking')); $form['tracking']['user_vis_settings']['googleanalytics_custom'] = array( '#type' => 'radios', '#title' => t('Allow users to customize tracking on their account page'), @@ -172,6 +187,12 @@ function googleanalytics_admin_settings_form($form_state) { ), '#default_value' => variable_get('googleanalytics_custom', 0), ); + $form['tracking']['user_vis_settings']['googleanalytics_trackuserid'] = array( + '#type' => 'checkbox', + '#title' => t('Track User ID'), + '#default_value' => variable_get('googleanalytics_trackuserid', 0), + '#description' => t('User ID enables the analysis of groups of sessions, across devices, using a unique, persistent, and non-personally identifiable ID string representing a user. Learn more about the benefits of using User ID.', array('@url' => 'https://support.google.com/analytics/answer/3123663')), + ); // Link specific configurations. $form['tracking']['linktracking'] = array( @@ -199,7 +220,40 @@ function googleanalytics_admin_settings_form($form_state) { '#type' => 'textfield', '#default_value' => variable_get('googleanalytics_trackfiles_extensions', GOOGLEANALYTICS_TRACKFILES_EXTENSIONS), '#description' => t('A file extension list separated by the | character that will be tracked as download when clicked. Regular expressions are supported. For example: !extensions', array('!extensions' => GOOGLEANALYTICS_TRACKFILES_EXTENSIONS)), - '#maxlength' => 255, + '#maxlength' => 500, + '#states' => array( + 'enabled' => array( + ':input[name="googleanalytics_trackfiles"]' => array('checked' => TRUE), + ), + // Note: Form required marker is not visible as title is invisible. + 'required' => array( + ':input[name="googleanalytics_trackfiles"]' => array('checked' => TRUE), + ), + ), + ); + + $colorbox_dependencies = '
'; + $colorbox_dependencies .= t('Requires: !module-list', array('!module-list' => (module_exists('colorbox') ? t('@module (enabled)', array('@module' => 'Colorbox')) : t('@module (disabled)', array('@module' => 'Colorbox'))))); + $colorbox_dependencies .= '
'; + + $form['tracking']['linktracking']['googleanalytics_trackcolorbox'] = array( + '#type' => 'checkbox', + '#title' => t('Track content in colorbox modal dialogs'), + '#default_value' => variable_get('googleanalytics_trackcolorbox', 1), + '#description' => t('Enable to track the content shown in colorbox modal windows.') . $colorbox_dependencies, + '#disabled' => (module_exists('colorbox') ? FALSE : TRUE), + ); + $form['tracking']['linktracking']['googleanalytics_tracklinkid'] = array( + '#type' => 'checkbox', + '#title' => t('Track enhanced link attribution'), + '#default_value' => variable_get('googleanalytics_tracklinkid', 0), + '#description' => t('Enhanced Link Attribution improves the accuracy of your In-Page Analytics report by automatically differentiating between multiple links to the same URL on a single page by using link element IDs. Enable enhanced link attribution in the Admin UI of your Google Analytics account.', array('@url' => 'https://support.google.com/analytics/answer/2558867')), + ); + $form['tracking']['linktracking']['googleanalytics_trackurlfragments'] = array( + '#type' => 'checkbox', + '#title' => t('Track changing URL fragments as pageviews'), + '#default_value' => variable_get('googleanalytics_trackurlfragments', 0), + '#description' => t('By default, the URL reported to Google Analytics will not include the "fragment identifier" (i.e. the portion of the URL beginning with a hash sign), and hash changes by themselves will not cause new pageviews to be reported. Checking this box will cause hash changes to be reported as pageviews (in modern browsers) and all pageview URLs to include the fragment where applicable.'), ); // Message specific configurations. @@ -211,7 +265,7 @@ function googleanalytics_admin_settings_form($form_state) { '#type' => 'checkboxes', '#title' => t('Track messages of type'), '#default_value' => variable_get('googleanalytics_trackmessages', array()), - '#description' => t('This will track the selected message types shown to users. Tracking of form validation errors may help you identifying usability issues in your site. Keep in mind that Google allows a maximum of 500 events per session only and every message is tracked as one individual event. If the limit has been exceeded no further events are tracked, but normal page tracking is not effected. Messages from excluded pages cannot tracked.'), + '#description' => t('This will track the selected message types shown to users. Tracking of form validation errors may help you identifying usability issues in your site. For each visit (user session), a maximum of approximately 500 combined GATC requests (both events and page views) can be tracked. Every message is tracked as one individual event. Note that - as the number of events in a session approaches the limit - additional events might not be tracked. Messages from excluded pages cannot be tracked.'), '#options' => array( 'status' => t('Status message'), 'warning' => t('Warning message'), @@ -219,21 +273,19 @@ function googleanalytics_admin_settings_form($form_state) { ), ); - // Google already have many translations, if not - they display a note to change the language. - global $language; $form['tracking']['search_and_advertising'] = array( '#type' => 'fieldset', '#title' => t('Search and Advertising'), ); - $site_search_dependencies = '
'; - $site_search_dependencies .= t('Depends on: !dependencies', array('!dependencies' => (module_exists('search') ? t('@module (enabled)', array('@module' => 'Search')) : t('@module (disabled)', array('@module' => 'Search'))))); + $site_search_dependencies = '
'; + $site_search_dependencies .= t('Requires: !module-list', array('!module-list' => (module_exists('search') ? t('@module (enabled)', array('@module' => 'Search')) : t('@module (disabled)', array('@module' => 'Search'))))); $site_search_dependencies .= '
'; $form['tracking']['search_and_advertising']['googleanalytics_site_search'] = array( '#type' => 'checkbox', '#title' => t('Track internal search'), - '#description' => t('If checked, internal search keywords are tracked. You must configure your Google account to use the internal query parameter search. For more information see Setting Up Site Search for a Profile.', array('@url' => url('http://support.google.com/analytics/bin/answer.py', array('query' => array('answer' => '1012264'))))) . $site_search_dependencies, + '#description' => t('If checked, internal search keywords are tracked. You must configure your Google account to use the internal query parameter search. For more information see Setting Up Site Search for a Profile.', array('@url' => 'https://support.google.com/analytics/answer/1012264')) . $site_search_dependencies, '#default_value' => variable_get('googleanalytics_site_search', FALSE), '#disabled' => (module_exists('search') ? FALSE : TRUE), ); @@ -245,8 +297,8 @@ function googleanalytics_admin_settings_form($form_state) { ); $form['tracking']['search_and_advertising']['googleanalytics_trackdoubleclick'] = array( '#type' => 'checkbox', - '#title' => t('Track DoubleClick data'), - '#description' => t('If checked, the alternative Google DoubleClick data tracking is used to enable AdWords remarketing features. If you choose this option you will need to update your privacy policy.', array('@doubleclick' => url('http://support.google.com/analytics/bin/answer.py', array('query' => array('answer' => '2444872'))), '@privacy' => url('http://support.google.com/analytics/bin/answer.py', array('query' => array('answer' => '2636405'))))), + '#title' => t('Track display features'), + '#description' => t('The display features plugin can be used to enable Display Advertising Features in Google Analytics, such as Remarketing, Demographics and Interest Reporting, and more. Learn more about Display Advertising Features in Google Analytics. If you choose this option you will need to update your privacy policy.', array('@displayfeatures' => 'https://support.google.com/analytics/answer/3450482', '@privacy' => 'https://support.google.com/analytics/answer/2700409')), '#default_value' => variable_get('googleanalytics_trackdoubleclick', FALSE), ); @@ -259,88 +311,119 @@ function googleanalytics_admin_settings_form($form_state) { '#type' => 'checkbox', '#title' => t('Anonymize visitors IP address'), '#description' => t('Tell Google Analytics to anonymize the information sent by the tracker objects by removing the last octet of the IP address prior to its storage. Note that this will slightly reduce the accuracy of geographic reporting. In some countries it is not allowed to collect personally identifying information for privacy reasons and this setting may help you to comply with the local laws.'), - '#default_value' => variable_get('googleanalytics_tracker_anonymizeip', 0), + '#default_value' => variable_get('googleanalytics_tracker_anonymizeip', 1), ); $form['tracking']['privacy']['googleanalytics_privacy_donottrack'] = array( '#type' => 'checkbox', '#title' => t('Universal web tracking opt-out'), - '#description' => t('If enabled and your server receives the Do-Not-Track header from the client browser, the Google Analytics module will not embed any tracking code into your site. Compliance with Do Not Track could be purely voluntary, enforced by industry self-regulation, or mandated by state or federal law. Please accept your visitors privacy. If they have opt-out from tracking and advertising, you should accept their personal decision. This feature is currently limited to logged in users and disabled page caching.', array('@donottrack' => 'http://donottrack.us/')), + '#description' => t('If enabled and your server receives the Do-Not-Track header from the client browser, the Google Analytics module will not embed any tracking code into your site. Compliance with Do Not Track could be purely voluntary, enforced by industry self-regulation, or mandated by state or federal law. Please accept your visitors privacy. If they have opt-out from tracking and advertising, you should accept their personal decision. This feature is currently limited to logged in users and disabled page caching.', array('@donottrack' => 'https://www.eff.org/issues/do-not-track')), '#default_value' => variable_get('googleanalytics_privacy_donottrack', 1), ); - // Custom variables. - $form['googleanalytics_custom_var'] = array( + // Custom Dimensions. + $form['googleanalytics_custom_dimension'] = array( '#collapsed' => TRUE, '#collapsible' => TRUE, - '#description' => t('You can add Google Analytics Custom Variables here. These will be added to every page that Google Analytics tracking code appears on. Google Analytics will only accept custom variables if the name and value combined are less than 128 bytes after URL encoding. Keep the names as short as possible and expect long values to get trimmed. You may use tokens in custom variable names and values. Global and user tokens are always available; on node pages, node tokens are also available.', array('@custom_var_documentation' => 'https://developers.google.com/analytics/devguides/collection/gajs/gaTrackingCustomVariables')), + '#description' => t('You can set values for Google Analytics Custom Dimensions here. You must have already configured your custom dimensions in the Google Analytics Management Interface. You may use tokens. Global and user tokens are always available; on node pages, node tokens are also available. A dimension value is allowed to have a maximum length of 150 bytes. Expect longer values to get trimmed.', array('@custom_var_documentation' => 'https://developers.google.com/analytics/devguides/collection/analyticsjs/custom-dims-mets', '@setup_documentation' => 'https://support.google.com/analytics/answer/2709829')), '#theme' => 'googleanalytics_admin_custom_var_table', - '#title' => t('Custom variables'), + '#title' => t('Custom dimensions'), '#tree' => TRUE, '#type' => 'fieldset', ); - $googleanalytics_custom_vars = variable_get('googleanalytics_custom_var', array()); + $googleanalytics_custom_dimension = variable_get('googleanalytics_custom_dimension', array()); - // Google Analytics supports up to 5 custom variables. - for ($i = 1; $i < 6; $i++) { - $form['googleanalytics_custom_var']['slots'][$i]['slot'] = array( + // Standard Google Analytics accounts support up to 20 custom dimensions, + // premium accounts support up to 200 custom dimensions. + $limit = (variable_get('googleanalytics_premium', FALSE)) ? 200 : 20; + for ($i = 1; $i <= $limit; $i++) { + $form['googleanalytics_custom_dimension']['indexes'][$i]['index'] = array( '#default_value' => $i, - '#description' => t('Slot number'), + '#description' => t('Index number'), '#disabled' => TRUE, '#size' => 1, - '#title' => t('Custom variable slot #@slot', array('@slot' => $i)), + '#title' => t('Custom dimension index #@index', array('@index' => $i)), '#title_display' => 'invisible', '#type' => 'textfield', ); - $form['googleanalytics_custom_var']['slots'][$i]['name'] = array( - '#default_value' => !empty($googleanalytics_custom_vars['slots'][$i]['name']) ? $googleanalytics_custom_vars['slots'][$i]['name'] : '', - '#description' => t('The custom variable name.'), + $form['googleanalytics_custom_dimension']['indexes'][$i]['value'] = array( + '#default_value' => isset($googleanalytics_custom_dimension[$i]['value']) ? $googleanalytics_custom_dimension[$i]['value'] : '', + '#description' => t('The custom dimension value.'), '#maxlength' => 255, - '#size' => 20, - '#title' => t('Custom variable name #@slot', array('@slot' => $i)), - '#title_display' => 'invisible', - '#type' => 'textfield', - '#element_validate' => array('googleanalytics_token_element_validate'), - '#token_types' => array('node'), - ); - $form['googleanalytics_custom_var']['slots'][$i]['value'] = array( - '#default_value' => !empty($googleanalytics_custom_vars['slots'][$i]['value']) ? $googleanalytics_custom_vars['slots'][$i]['value'] : '', - '#description' => t('The custom variable value.'), - '#maxlength' => 255, - '#title' => t('Custom variable value #@slot', array('@slot' => $i)), + '#title' => t('Custom dimension value #@index', array('@index' => $i)), '#title_display' => 'invisible', '#type' => 'textfield', '#element_validate' => array('googleanalytics_token_element_validate'), '#token_types' => array('node'), ); if (module_exists('token')) { - $form['googleanalytics_custom_var']['slots'][$i]['name']['#element_validate'][] = 'token_element_validate'; - $form['googleanalytics_custom_var']['slots'][$i]['value']['#element_validate'][] = 'token_element_validate'; + $form['googleanalytics_custom_dimension']['indexes'][$i]['value']['#element_validate'][] = 'token_element_validate'; } - $form['googleanalytics_custom_var']['slots'][$i]['scope'] = array( - '#default_value' => !empty($googleanalytics_custom_vars['slots'][$i]['scope']) ? $googleanalytics_custom_vars['slots'][$i]['scope'] : 3, - '#description' => t('The scope for the custom variable.'), - '#title' => t('Custom variable slot #@slot', array('@slot' => $i)), - '#title_display' => 'invisible', - '#type' => 'select', - '#options' => array( - 1 => t('Visitor'), - 2 => t('Session'), - 3 => t('Page'), - ), + } + + $form['googleanalytics_custom_dimension']['googleanalytics_description'] = array( + '#type' => 'item', + '#description' => t('You can supplement Google Analytics\' basic IP address tracking of visitors by segmenting users based on custom dimensions. Section 7 of the Google Analytics terms of service requires that You will not (and will not allow any third party to) use the Service to track, collect or upload any data that personally identifies an individual (such as a name, userid, email address or billing information), or other data which can be reasonably linked to such information by Google. You will have and abide by an appropriate Privacy Policy and will comply with all applicable laws and regulations relating to the collection of information from Visitors. You must post a Privacy Policy and that Privacy Policy must provide notice of Your use of cookies that are used to collect traffic data, and You must not circumvent any privacy features (e.g., an opt-out) that are part of the Service.', array('@ga_tos' => 'https://www.google.com/analytics/terms/gb.html')), + ); + if (module_exists('token')) { + $form['googleanalytics_custom_dimension']['googleanalytics_token_tree'] = array( + '#theme' => 'token_tree', + '#token_types' => array('node'), + '#dialog' => TRUE, ); } - $form['googleanalytics_custom_var']['googleanalytics_custom_var_description'] = array( - '#type' => 'item', - '#description' => t('You can supplement Google Analytics\' basic IP address tracking of visitors by segmenting users based on custom variables. Section 7 of the Google Analytics terms of service requires that You will not (and will not allow any third party to) use the Service to track, collect or upload any data that personally identifies an individual (such as a name, email address or billing information), or other data which can be reasonably linked to such information by Google. You will have and abide by an appropriate Privacy Policy and will comply with all applicable laws and regulations relating to the collection of information from Visitors. You must post a Privacy Policy and that Privacy Policy must provide notice of Your use of cookies that are used to collect traffic data, and You must not circumvent any privacy features (e.g., an opt-out) that are part of the Service.', array('@ga_tos' => 'http://www.google.com/analytics/terms/gb.html')), - ); - $form['googleanalytics_custom_var']['googleanalytics_custom_var_token_tree'] = array( - '#theme' => 'token_tree', - '#token_types' => array('node'), - '#dialog' => TRUE, + // Custom Metrics. + $form['googleanalytics_custom_metric'] = array( + '#collapsed' => TRUE, + '#collapsible' => TRUE, + '#description' => t('You can add Google Analytics Custom Metrics here. You must have already configured your custom metrics in the Google Analytics Management Interface. You may use tokens. Global and user tokens are always available; on node pages, node tokens are also available.', array('@custom_var_documentation' => 'https://developers.google.com/analytics/devguides/collection/analyticsjs/custom-dims-mets', '@setup_documentation' => 'https://support.google.com/analytics/answer/2709829')), + '#theme' => 'googleanalytics_admin_custom_var_table', + '#title' => t('Custom metrics'), + '#tree' => TRUE, + '#type' => 'fieldset', ); + $googleanalytics_custom_metric = variable_get('googleanalytics_custom_metric', array()); + + // Standard Google Analytics accounts support up to 20 custom metrics, + // premium accounts support up to 200 custom metrics. + for ($i = 1; $i <= $limit; $i++) { + $form['googleanalytics_custom_metric']['indexes'][$i]['index'] = array( + '#default_value' => $i, + '#description' => t('Index number'), + '#disabled' => TRUE, + '#size' => 1, + '#title' => t('Custom metric index #@index', array('@index' => $i)), + '#title_display' => 'invisible', + '#type' => 'textfield', + ); + $form['googleanalytics_custom_metric']['indexes'][$i]['value'] = array( + '#default_value' => isset($googleanalytics_custom_metric[$i]['value']) ? $googleanalytics_custom_metric[$i]['value'] : '', + '#description' => t('The custom metric value.'), + '#maxlength' => 255, + '#title' => t('Custom metric value #@index', array('@index' => $i)), + '#title_display' => 'invisible', + '#type' => 'textfield', + '#element_validate' => array('googleanalytics_token_element_validate'), + '#token_types' => array('node'), + ); + if (module_exists('token')) { + $form['googleanalytics_custom_metric']['indexes'][$i]['value']['#element_validate'][] = 'token_element_validate'; + } + } + + $form['googleanalytics_custom_metric']['googleanalytics_description'] = array( + '#type' => 'item', + '#description' => t('You can supplement Google Analytics\' basic IP address tracking of visitors by segmenting users based on custom metrics. Section 7 of the Google Analytics terms of service requires that You will not (and will not allow any third party to) use the Service to track, collect or upload any data that personally identifies an individual (such as a name, userid, email address or billing information), or other data which can be reasonably linked to such information by Google. You will have and abide by an appropriate Privacy Policy and will comply with all applicable laws and regulations relating to the collection of information from Visitors. You must post a Privacy Policy and that Privacy Policy must provide notice of Your use of cookies that are used to collect traffic data, and You must not circumvent any privacy features (e.g., an opt-out) that are part of the Service.', array('@ga_tos' => 'https://www.google.com/analytics/terms/gb.html')), + ); + if (module_exists('token')) { + $form['googleanalytics_custom_metric']['googleanalytics_token_tree'] = array( + '#theme' => 'token_tree', + '#token_types' => array('node'), + '#dialog' => TRUE, + ); + } // Advanced feature configurations. $form['advanced'] = array( @@ -367,38 +450,45 @@ function googleanalytics_admin_settings_form($form_state) { ); } + $user_access_add_js_snippets = !user_access('add JS snippets for google analytics'); + $user_access_add_js_snippets_permission_warning = $user_access_add_js_snippets ? ' ' . t('This field has been disabled because you do not have sufficient permissions to edit it.') . '' : ''; $form['advanced']['codesnippet'] = array( '#type' => 'fieldset', '#title' => t('Custom JavaScript code'), '#collapsible' => TRUE, '#collapsed' => TRUE, - '#description' => t('You can add custom Google Analytics code snippets here. These will be added every time tracking is in effect. Before you add your custom code, you should read the Google Analytics Tracking Code - Functional Overview and the Google Analytics Tracking API documentation. Do not include the <script> tags, and always end your code with a semicolon (;).', array('@snippets' => 'http://drupal.org/node/248699', '@ga_concepts_overview' => 'https://developers.google.com/analytics/resources/concepts/gaConceptsTrackingOverview', '@ga_js_api' => 'https://developers.google.com/analytics/devguides/collection/gajs/methods/')), + '#description' => t('You can add custom Google Analytics code snippets here. These will be added every time tracking is in effect. Before you add your custom code, you should read the Google Analytics Tracking Code - Functional Overview and the Google Analytics Tracking API documentation. Do not include the <script> tags, and always end your code with a semicolon (;).', array('@snippets' => 'https://drupal.org/node/248699', '@ga_concepts_overview' => 'https://developers.google.com/analytics/resources/concepts/gaConceptsTrackingOverview', '@ga_js_api' => 'https://developers.google.com/analytics/devguides/collection/analyticsjs/method-reference')), + ); + $form['advanced']['codesnippet']['googleanalytics_codesnippet_create'] = array( + '#type' => 'textarea', + '#title' => t('Create only fields'), + '#default_value' => _googleanalytics_get_name_value_string(variable_get('googleanalytics_codesnippet_create', array())), + '#rows' => 5, + '#description' => t('Enter one value per line, in the format name|value. Settings in this textarea will be added to ga("create", "UA-XXXX-Y", {"name":"value"});. For more information, read create only fields documentation in the Analytics.js field reference.', array('@url' => 'https://developers.google.com/analytics/devguides/collection/analyticsjs/field-reference#create')), + '#element_validate' => array('googleanalytics_validate_create_field_values'), ); $form['advanced']['codesnippet']['googleanalytics_codesnippet_before'] = array( '#type' => 'textarea', '#title' => t('Code snippet (before)'), '#default_value' => variable_get('googleanalytics_codesnippet_before', ''), + '#disabled' => $user_access_add_js_snippets, '#rows' => 5, - '#description' => t("Code in this textarea will be added before _gaq.push(['_trackPageview'])."), + '#description' => t('Code in this textarea will be added before ga("send", "pageview");.') . $user_access_add_js_snippets_permission_warning, ); $form['advanced']['codesnippet']['googleanalytics_codesnippet_after'] = array( '#type' => 'textarea', '#title' => t('Code snippet (after)'), '#default_value' => variable_get('googleanalytics_codesnippet_after', ''), + '#disabled' => $user_access_add_js_snippets, '#rows' => 5, - '#description' => t("Code in this textarea will be added after _gaq.push(['_trackPageview']). This is useful if you'd like to track a site in two accounts."), + '#description' => t('Code in this textarea will be added after ga("send", "pageview");. This is useful if you\'d like to track a site in two accounts.') . $user_access_add_js_snippets_permission_warning, ); - $form['advanced']['googleanalytics_js_scope'] = array( - '#type' => 'select', - '#title' => t('JavaScript scope'), - '#description' => t('Google recommends adding the external JavaScript files to the header for performance reasons. If Multiple top-level domains has been selected, this setting will be forced to header.'), - '#options' => array( - 'footer' => t('Footer'), - 'header' => t('Header'), - ), - '#default_value' => variable_get('googleanalytics_js_scope', 'header'), - '#disabled' => (variable_get('googleanalytics_domain_mode', 0) == 2) ? TRUE : FALSE, + $form['advanced']['googleanalytics_debug'] = array( + '#type' => 'checkbox', + '#title' => t('Enable debugging'), + '#description' => t('If checked, the Google Universal Analytics debugging script will be loaded. You should not enable your production site to use this version of the JavaScript. The analytics_debug.js script is larger than the analytics.js tracking code and it is not typically cached. Using it in your production site will slow down your site for all of your users. Again, this is only for your own testing purposes. Debug messages are printed to the window.console object.'), + '#default_value' => variable_get('googleanalytics_debug', 0), ); return system_settings_form($form); @@ -408,31 +498,37 @@ function googleanalytics_admin_settings_form($form_state) { * Implements _form_validate(). */ function googleanalytics_admin_settings_form_validate($form, &$form_state) { - // Custom variables validation. - foreach ($form_state['values']['googleanalytics_custom_var']['slots'] as $custom_var) { - $form_state['values']['googleanalytics_custom_var']['slots'][$custom_var['slot']]['name'] = trim($custom_var['name']); - $form_state['values']['googleanalytics_custom_var']['slots'][$custom_var['slot']]['value'] = trim($custom_var['value']); - - // Validate empty names/values. - if (empty($custom_var['name']) && !empty($custom_var['value'])) { - form_set_error("googleanalytics_custom_var][slots][" . $custom_var['slot'] . "][name", t('The custom variable @slot-number requires a Name if a Value has been provided.', array('@slot-number' => $custom_var['slot']))); - } - elseif (!empty($custom_var['name']) && empty($custom_var['value'])) { - form_set_error("googleanalytics_custom_var][slots][" . $custom_var['slot'] . "][value", t('The custom variable @slot-number requires a Value if a Name has been provided.', array('@slot-number' => $custom_var['slot']))); + // Trim custom dimensions and metrics. + foreach ($form_state['values']['googleanalytics_custom_dimension']['indexes'] as $dimension) { + $form_state['values']['googleanalytics_custom_dimension']['indexes'][$dimension['index']]['value'] = trim($dimension['value']); + // Remove empty values from the array. + if (!drupal_strlen($form_state['values']['googleanalytics_custom_dimension']['indexes'][$dimension['index']]['value'])) { + unset($form_state['values']['googleanalytics_custom_dimension']['indexes'][$dimension['index']]); } } + $form_state['values']['googleanalytics_custom_dimension'] = $form_state['values']['googleanalytics_custom_dimension']['indexes']; + + foreach ($form_state['values']['googleanalytics_custom_metric']['indexes'] as $metric) { + $form_state['values']['googleanalytics_custom_metric']['indexes'][$metric['index']]['value'] = trim($metric['value']); + // Remove empty values from the array. + if (!drupal_strlen($form_state['values']['googleanalytics_custom_metric']['indexes'][$metric['index']]['value'])) { + unset($form_state['values']['googleanalytics_custom_metric']['indexes'][$metric['index']]); + } + } + $form_state['values']['googleanalytics_custom_metric'] = $form_state['values']['googleanalytics_custom_metric']['indexes']; // Trim some text values. $form_state['values']['googleanalytics_account'] = trim($form_state['values']['googleanalytics_account']); $form_state['values']['googleanalytics_pages'] = trim($form_state['values']['googleanalytics_pages']); $form_state['values']['googleanalytics_cross_domains'] = trim($form_state['values']['googleanalytics_cross_domains']); + $form_state['values']['googleanalytics_codesnippet_create'] = _googleanalytics_extract_create_field_values($form_state['values']['googleanalytics_codesnippet_create']); $form_state['values']['googleanalytics_codesnippet_before'] = trim($form_state['values']['googleanalytics_codesnippet_before']); $form_state['values']['googleanalytics_codesnippet_after'] = trim($form_state['values']['googleanalytics_codesnippet_after']); // Replace all type of dashes (n-dash, m-dash, minus) with the normal dashes. $form_state['values']['googleanalytics_account'] = str_replace(array('–', '—', '−'), '-', $form_state['values']['googleanalytics_account']); - if (!preg_match('/^UA-\d{4,}-\d+$/', $form_state['values']['googleanalytics_account'])) { + if (!preg_match('/^UA-\d+-\d+$/', $form_state['values']['googleanalytics_account'])) { form_set_error('googleanalytics_account', t('A valid Google Analytics Web Property ID is case sensitive and formatted like UA-xxxxxxx-yy.')); } @@ -440,16 +536,25 @@ function googleanalytics_admin_settings_form_validate($form, &$form_state) { if ($form_state['values']['googleanalytics_domain_mode'] == 2 && empty($form_state['values']['googleanalytics_cross_domains'])) { form_set_error('googleanalytics_cross_domains', t('A list of top-level domains is required if Multiple top-level domains has been selected.')); } + // Clear the domain list if cross domains are disabled. + if ($form_state['values']['googleanalytics_domain_mode'] != 2) { + $form_state['values']['googleanalytics_cross_domains'] = ''; + } + + // Disallow empty list of download file extensions. + if ($form_state['values']['googleanalytics_trackfiles'] && empty($form_state['values']['googleanalytics_trackfiles_extensions'])) { + form_set_error('googleanalytics_trackfiles_extensions', t('List of download file extensions cannot empty.')); + } // Clear obsolete local cache if cache has been disabled. if (empty($form_state['values']['googleanalytics_cache']) && $form['advanced']['googleanalytics_cache']['#default_value']) { googleanalytics_clear_js_cache(); } // This is for the Newbie's who cannot read a text area description. - if (stristr($form_state['values']['googleanalytics_codesnippet_before'], 'google-analytics.com/ga.js')) { + if (stristr($form_state['values']['googleanalytics_codesnippet_before'], 'google-analytics.com/analytics.js')) { form_set_error('googleanalytics_codesnippet_before', t('Do not add the tracker code provided by Google into the javascript code snippets! This module already builds the tracker code based on your Google Analytics account number and settings.')); } - if (stristr($form_state['values']['googleanalytics_codesnippet_after'], 'google-analytics.com/ga.js')) { + if (stristr($form_state['values']['googleanalytics_codesnippet_after'], 'google-analytics.com/analytics.js')) { form_set_error('googleanalytics_codesnippet_after', t('Do not add the tracker code provided by Google into the javascript code snippets! This module already builds the tracker code based on your Google Analytics account number and settings.')); } if (preg_match('/(.*)<\/?script(.*)>(.*)/i', $form_state['values']['googleanalytics_codesnippet_before'])) { @@ -458,41 +563,40 @@ function googleanalytics_admin_settings_form_validate($form, &$form_state) { if (preg_match('/(.*)<\/?script(.*)>(.*)/i', $form_state['values']['googleanalytics_codesnippet_after'])) { form_set_error('googleanalytics_codesnippet_after', t('Do not include the <script> tags in the javascript code snippets.')); } - - // Header section must be forced for multiple top-level domains. - if ($form_state['values']['googleanalytics_domain_mode'] == 2) { - $form_state['values']['googleanalytics_js_scope'] = 'header'; - } } /** * Layout for the custom variables table in the admin settings form. + * + * @param array $variables + * An array contains the form elements. + * + * @return string + * The rendered output. */ function theme_googleanalytics_admin_custom_var_table($variables) { $form = $variables['form']; $header = array( - array('data' => t('Slot')), - array('data' => t('Name')), + array('data' => t('Index')), array('data' => t('Value')), - array('data' => t('Scope')), ); $rows = array(); - foreach (element_children($form['slots']) as $key => $id) { + foreach (element_children($form['indexes']) as $id) { $rows[] = array( 'data' => array( - drupal_render($form['slots'][$id]['slot']), - drupal_render($form['slots'][$id]['name']), - drupal_render($form['slots'][$id]['value']), - drupal_render($form['slots'][$id]['scope']), + drupal_render($form['indexes'][$id]['index']), + drupal_render($form['indexes'][$id]['value']), ), ); } $output = theme('table', array('header' => $header, 'rows' => $rows)); - $output .= drupal_render($form['googleanalytics_custom_var_description']); - $output .= drupal_render($form['googleanalytics_custom_var_token_tree']); + $output .= drupal_render($form['googleanalytics_description']); + if (isset($form['googleanalytics_token_tree'])) { + $output .= drupal_render($form['googleanalytics_token_tree']); + } return $output; } @@ -528,11 +632,18 @@ function googleanalytics_token_element_validate(&$element, &$form_state) { return $element; } +/** + * @param array $value + * An array of token values. + * + * @return array + * A unique array of invalid tokens. + */ function _googleanalytics_get_forbidden_tokens($value) { $invalid_tokens = array(); $value_tokens = is_string($value) ? token_scan($value) : $value; - foreach ($value_tokens as $type => $tokens) { + foreach ($value_tokens as $tokens) { if (array_filter($tokens, '_googleanalytics_contains_forbidden_token')) { $invalid_tokens = array_merge($invalid_tokens, array_values($tokens)); } @@ -545,8 +656,9 @@ function _googleanalytics_get_forbidden_tokens($value) { /** * Validate if a string contains forbidden tokens not allowed by privacy rules. * - * @param $token_string + * @param string $token_string * A string with one or more tokens to be validated. + * * @return boolean * TRUE if blacklisted token has been found, otherwise FALSE. */ @@ -599,3 +711,203 @@ function _googleanalytics_contains_forbidden_token($token_string) { return preg_match('/' . implode('|', array_map('preg_quote', $token_blacklist)) . '/i', $token_string); } + +/** + * #element_validate callback for create only fields. + * + * @param $element + * An associative array containing the properties and children of the + * generic form element. + * @param $form_state + * The $form_state array for the form this element belongs to. + * + * @see form_process_pattern() + */ +function googleanalytics_validate_create_field_values(&$element, &$form_state) { + $values = _googleanalytics_extract_create_field_values($element['#value']); + + if (!is_array($values)) { + form_error($element, t('The %element-title field contains invalid input.', array('%element-title' => $element['#title']))); + } + else { + // Check that name and value are valid for the field type. + foreach ($values as $name => $value) { + if ($error = _googleanalytics_validate_create_field_name($name)) { + form_error($element, $error); + break; + } + if ($error = _googleanalytics_validate_create_field_value($value)) { + form_error($element, $error); + break; + } + } + + return $element; + } +} + +/** + * Extracts the values array from the element. + * + * @param string $string + * The raw string to extract values from. + * + * @return array|null + * The array of extracted key/value pairs, or NULL if the string is invalid. + * + * @see \Drupal\options\Plugin\Field\FieldType\ListTextItem::allowedValuesString() + */ +function _googleanalytics_extract_create_field_values($string) { + $values = array(); + + $list = explode("\n", $string); + $list = array_map('trim', $list); + $list = array_filter($list, 'strlen'); + + foreach ($list as $text) { + // Check for an explicit key. + $matches = array(); + if (preg_match('/(.*)\|(.*)/', $text, $matches)) { + // Trim key and value to avoid unwanted spaces issues. + $name = trim($matches[1]); + $value = trim($matches[2]); + } + else { + return; + } + + $values[$name] = $value; + } + + return _googleanalytics_convert_form_value_data_types($values); +} + +/** + * Checks whether a field name is valid. + * + * @param string $name + * The option value entered by the user. + * + * @return string + * The error message if the specified value is invalid, NULL otherwise. + */ +function _googleanalytics_validate_create_field_name($name) { + // List of supported field names: + // https://developers.google.com/analytics/devguides/collection/analyticsjs/field-reference#create + $create_only_fields = array( + 'allowAnchor', + 'alwaysSendReferrer', + 'clientId', + 'cookieName', + 'cookieDomain', + 'cookieExpires', + 'legacyCookieDomain', + 'legacyHistoryImport', + 'sampleRate', + 'siteSpeedSampleRate', + 'storage', + 'useAmpClientId', + ); + + if ($name == 'name') { + return t('Create only field name %name is a disallowed field name. Changing the Tracker Name is currently not supported.', array('%name' => $name)); + } + if ($name == 'allowLinker') { + return t('Create only field name %name is a disallowed field name. Please select Multiple top-level domains under What are you tracking to enable cross domain tracking.', array('%name' => $name)); + } + if ($name == 'userId') { + return t('Create only field name %name is a disallowed field name. Please enable Track User ID under Tracking scope > Users.', array('%name' => $name)); + } + if (!in_array($name, $create_only_fields)) { + return t('Create only field name %name is an unknown field name. Field names are case sensitive. Please see create only fields documentation for supported field names.', array('%name' => $name, '@url' => 'https://developers.google.com/analytics/devguides/collection/analyticsjs/field-reference#create')); + } +} + +/** + * Checks whether a field value is valid. + * + * @param string $value + * The option value entered by the user. + * + * @return string + * The error message if the specified value is invalid, NULL otherwise. + */ +function _googleanalytics_validate_create_field_value($value) { + if (!is_bool($value) && !drupal_strlen($value)) { + return t('A create only field requires a value.'); + } + if (drupal_strlen($value) > 255) { + return t('The value of a create only field must be a string at most 255 characters long.'); + } +} + +/** + * Generates a string representation of an array. + * + * This string format is suitable for edition in a textarea. + * + * @param array $values + * An array of values, where array keys are values and array values are + * labels. + * + * @return string + * The string representation of the $values array: + * - Values are separated by a carriage return. + * - Each value is in the format "name|value" or "value". + */ +function _googleanalytics_get_name_value_string($values) { + $lines = array(); + foreach ($values as $name => $value) { + // Convert data types. + // @todo: #2251377: Json utility class serializes boolean values to incorrect data type + if (is_bool($value)) { + $value = ($value) ? 'true' : 'false'; + } + + $lines[] = "$name|$value"; + } + return implode("\n", $lines); +} + +/** + * Prepare form data types for Json conversion. + * + * @param array $values + * Array of name/value pairs. + * + * @return array + * Array of name/value pairs with casted data types. + */ +function _googleanalytics_convert_form_value_data_types($values) { + + foreach ($values as $name => $value) { + // Convert data types. + // @todo: #2251377: Json utility class serializes boolean values to incorrect data type + $match = drupal_strtolower($value); + if ($match == 'true') { + $value = TRUE; + } + elseif ($match == 'false') { + $value = FALSE; + } + + // Convert other known fields. + // @todo: #2251343: Json utility class serializes numeric values to incorrect data type + switch ($name) { + case 'sampleRate': + // Float + settype($value, 'float'); + break; + + case 'siteSpeedSampleRate': + case 'cookieExpires': + // Integer + settype($value, 'integer'); + break; + } + + $values[$name] = $value; + } + + return $values; +} diff --git a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.admin.js b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.admin.js index 35da84e..c81470d 100644 --- a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.admin.js +++ b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.admin.js @@ -65,6 +65,15 @@ Drupal.behaviors.trackingSettingsSummary = { if ($('input#edit-googleanalytics-trackfiles', context).is(':checked')) { vals.push(Drupal.t('Downloads')); } + if ($('input#edit-googleanalytics-trackcolorbox', context).is(':checked')) { + vals.push(Drupal.t('Colorbox')); + } + if ($('input#edit-googleanalytics-tracklinkid', context).is(':checked')) { + vals.push(Drupal.t('Link attribution')); + } + if ($('input#edit-googleanalytics-trackurlfragments', context).is(':checked')) { + vals.push(Drupal.t('URL fragments')); + } if (!vals.length) { return Drupal.t('Not tracked'); } @@ -91,7 +100,7 @@ Drupal.behaviors.trackingSettingsSummary = { vals.push(Drupal.t('AdSense ads')); } if ($('input#edit-googleanalytics-trackdoubleclick', context).is(':checked')) { - vals.push(Drupal.t('DoubleClick data')); + vals.push(Drupal.t('Display features')); } if (!vals.length) { return Drupal.t('Not tracked'); diff --git a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.debug.js b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.debug.js new file mode 100644 index 0000000..7186230 --- /dev/null +++ b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.debug.js @@ -0,0 +1,213 @@ +(function ($) { + +Drupal.googleanalytics = {}; + +$(document).ready(function() { + + // Attach mousedown, keyup, touchstart events to document only and catch + // clicks on all elements. + $(document.body).bind("mousedown keyup touchstart", function(event) { + console.group("Running Google Analytics for Drupal."); + console.info("Event '%s' has been detected.", event.type); + + // Catch the closest surrounding link of a clicked element. + $(event.target).closest("a,area").each(function() { + console.info("Closest element '%o' has been found. URL '%s' extracted.", this, this.href); + + // Is the clicked URL internal? + if (Drupal.googleanalytics.isInternal(this.href)) { + // Skip 'click' tracking, if custom tracking events are bound. + if ($(this).is('.colorbox') && (Drupal.settings.googleanalytics.trackColorbox)) { + // Do nothing here. The custom event will handle all tracking. + console.info("Click on .colorbox item has been detected."); + } + // Is download tracking activated and the file extension configured for download tracking? + else if (Drupal.settings.googleanalytics.trackDownload && Drupal.googleanalytics.isDownload(this.href)) { + // Download link clicked. + console.info("Download url '%s' has been found. Tracked download as extension '%s'.", Drupal.googleanalytics.getPageUrl(this.href), Drupal.googleanalytics.getDownloadExtension(this.href).toUpperCase()); + ga("send", { + "hitType": "event", + "eventCategory": "Downloads", + "eventAction": Drupal.googleanalytics.getDownloadExtension(this.href).toUpperCase(), + "eventLabel": Drupal.googleanalytics.getPageUrl(this.href), + "transport": "beacon" + }); + } + else if (Drupal.googleanalytics.isInternalSpecial(this.href)) { + // Keep the internal URL for Google Analytics website overlay intact. + console.info("Click on internal special link '%s' has been tracked.", Drupal.googleanalytics.getPageUrl(this.href)); + ga("send", { + "hitType": "pageview", + "page": Drupal.googleanalytics.getPageUrl(this.href), + "transport": "beacon" + }); + } + else { + // e.g. anchor in same page or other internal page link + console.info("Click on internal link '%s' detected, but not tracked by click.", this.href); + } + } + else { + if (Drupal.settings.googleanalytics.trackMailto && $(this).is("a[href^='mailto:'],area[href^='mailto:']")) { + // Mailto link clicked. + console.info("Click on e-mail '%s' has been tracked.", this.href.substring(7)); + ga("send", { + "hitType": "event", + "eventCategory": "Mails", + "eventAction": "Click", + "eventLabel": this.href.substring(7), + "transport": "beacon" + }); + } + else if (Drupal.settings.googleanalytics.trackOutbound && this.href.match(/^\w+:\/\//i)) { + if (Drupal.settings.googleanalytics.trackDomainMode !== 2 || (Drupal.settings.googleanalytics.trackDomainMode === 2 && !Drupal.googleanalytics.isCrossDomain(this.hostname, Drupal.settings.googleanalytics.trackCrossDomains))) { + // External link clicked / No top-level cross domain clicked. + console.info("Outbound link '%s' has been tracked.", this.href); + ga("send", { + "hitType": "event", + "eventCategory": "Outbound links", + "eventAction": "Click", + "eventLabel": this.href, + "transport": "beacon" + }); + } + else { + console.info("Internal link '%s' clicked, not tracked.", this.href); + } + } + } + }); + + console.groupEnd(); + }); + + // Track hash changes as unique pageviews, if this option has been enabled. + if (Drupal.settings.googleanalytics.trackUrlFragments) { + window.onhashchange = function() { + console.info("Track URL '%s' as pageview. Hash '%s' has changed.", location.pathname + location.search + location.hash, location.hash); + ga("send", { + "hitType": "pageview", + "page": location.pathname + location.search + location.hash + }); + }; + } + + // Colorbox: This event triggers when the transition has completed and the + // newly loaded content has been revealed. + if (Drupal.settings.googleanalytics.trackColorbox) { + $(document).bind("cbox_complete", function () { + var href = $.colorbox.element().attr("href"); + if (href) { + console.info("Colorbox transition to url '%s' has been tracked.", Drupal.googleanalytics.getPageUrl(href)); + ga("send", { + "hitType": "pageview", + "page": Drupal.googleanalytics.getPageUrl(href) + }); + } + }); + } + +}); + +/** + * Check whether the hostname is part of the cross domains or not. + * + * @param string hostname + * The hostname of the clicked URL. + * @param array crossDomains + * All cross domain hostnames as JS array. + * + * @return boolean + */ +Drupal.googleanalytics.isCrossDomain = function (hostname, crossDomains) { + /** + * jQuery < 1.6.3 bug: $.inArray crushes IE6 and Chrome if second argument is + * `null` or `undefined`, https://bugs.jquery.com/ticket/10076, + * https://github.com/jquery/jquery/commit/a839af034db2bd934e4d4fa6758a3fed8de74174 + * + * @todo: Remove/Refactor in D8 + */ + if (!crossDomains) { + return false; + } + else { + return $.inArray(hostname, crossDomains) > -1 ? true : false; + } +}; + +/** + * Check whether this is a download URL or not. + * + * @param string url + * The web url to check. + * + * @return boolean + */ +Drupal.googleanalytics.isDownload = function (url) { + var isDownload = new RegExp("\\.(" + Drupal.settings.googleanalytics.trackDownloadExtensions + ")([\?#].*)?$", "i"); + return isDownload.test(url); +}; + +/** + * Check whether this is an absolute internal URL or not. + * + * @param string url + * The web url to check. + * + * @return boolean + */ +Drupal.googleanalytics.isInternal = function (url) { + var isInternal = new RegExp("^(https?):\/\/" + window.location.host, "i"); + return isInternal.test(url); +}; + +/** + * Check whether this is a special URL or not. + * + * URL types: + * - gotwo.module /go/* links. + * + * @param string url + * The web url to check. + * + * @return boolean + */ +Drupal.googleanalytics.isInternalSpecial = function (url) { + var isInternalSpecial = new RegExp("(\/go\/.*)$", "i"); + return isInternalSpecial.test(url); +}; + +/** + * Extract the relative internal URL from an absolute internal URL. + * + * Examples: + * - https://mydomain.com/node/1 -> /node/1 + * - https://example.com/foo/bar -> https://example.com/foo/bar + * + * @param string url + * The web url to check. + * + * @return string + * Internal website URL + */ +Drupal.googleanalytics.getPageUrl = function (url) { + var extractInternalUrl = new RegExp("^(https?):\/\/" + window.location.host, "i"); + return url.replace(extractInternalUrl, ''); +}; + +/** + * Extract the download file extension from the URL. + * + * @param string url + * The web url to check. + * + * @return string + * The file extension of the passed url. e.g. "zip", "txt" + */ +Drupal.googleanalytics.getDownloadExtension = function (url) { + var extractDownloadextension = new RegExp("\\.(" + Drupal.settings.googleanalytics.trackDownloadExtensions + ")([\?#].*)?$", "i"); + var extension = extractDownloadextension.exec(url); + return (extension === null) ? '' : extension[1]; +}; + +})(jQuery); diff --git a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.info b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.info index fea44ca..591816a 100644 --- a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.info +++ b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.info @@ -4,10 +4,9 @@ core = 7.x package = Statistics configure = admin/config/system/googleanalytics files[] = googleanalytics.test - -; Information added by drupal.org packaging script on 2012-11-01 -version = "7.x-1.3" +test_dependencies[] = token +; Information added by Drupal.org packaging script on 2019-01-31 +version = "7.x-2.6" core = "7.x" project = "google_analytics" -datestamp = "1351810914" - +datestamp = "1548968597" diff --git a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.install b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.install index b3841cb..08b4e05 100644 --- a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.install +++ b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.install @@ -5,52 +5,46 @@ * Installation file for Google Analytics module. */ -/** - * Implements hook_install(). - */ -function googleanalytics_install() { - // By German laws it's always best to enable the anonymizing of IP addresses. - // NOTE: If this is also an important default setting in other countries, please let us know! - $countries = array( - 'DE', - ); - if (in_array(variable_get('site_default_country', ''), $countries)) { - variable_set('googleanalytics_tracker_anonymizeip', 1); - } -} - /** * Implements hook_uninstall(). */ function googleanalytics_uninstall() { variable_del('googleanalytics_account'); + variable_del('googleanalytics_premium'); variable_del('googleanalytics_cache'); + variable_del('googleanalytics_codesnippet_create'); variable_del('googleanalytics_codesnippet_before'); variable_del('googleanalytics_codesnippet_after'); variable_del('googleanalytics_cross_domains'); variable_del('googleanalytics_custom'); - variable_del('googleanalytics_custom_var'); + variable_del('googleanalytics_custom_dimension'); + variable_del('googleanalytics_custom_metric'); + variable_del('googleanalytics_debug'); variable_del('googleanalytics_domain_mode'); - variable_del('googleanalytics_js_scope'); variable_del('googleanalytics_last_cache'); variable_del('googleanalytics_pages'); variable_del('googleanalytics_roles'); variable_del('googleanalytics_site_search'); variable_del('googleanalytics_trackadsense'); + variable_del('googleanalytics_trackcolorbox'); variable_del('googleanalytics_trackdoubleclick'); variable_del('googleanalytics_tracker_anonymizeip'); variable_del('googleanalytics_trackfiles'); variable_del('googleanalytics_trackfiles_extensions'); + variable_del('googleanalytics_tracklinkid'); + variable_del('googleanalytics_trackurlfragments'); + variable_del('googleanalytics_trackuserid'); variable_del('googleanalytics_trackmailto'); + variable_del('googleanalytics_trackmessages'); variable_del('googleanalytics_trackoutbound'); variable_del('googleanalytics_translation_set'); variable_del('googleanalytics_visibility_pages'); variable_del('googleanalytics_visibility_roles'); + variable_del('googleanalytics_privacy_donottrack'); - // Remove backup variables if exist. Remove this code in D8. - variable_del('googleanalytics_codesnippet_after_backup_6300'); - variable_del('googleanalytics_codesnippet_before_backup_6300'); - variable_del('googleanalytics_segmentation'); + // Remove backup variables if they exist. Remove this code in D8. + variable_del('googleanalytics_codesnippet_after_backup_7200'); + variable_del('googleanalytics_codesnippet_before_backup_7200'); } /** @@ -71,21 +65,32 @@ function googleanalytics_requirements($phase) { if ($phase == 'runtime') { // Raise warning if Google user account has not been set yet. - if (!preg_match('/^UA-\d{4,}-\d+$/', variable_get('googleanalytics_account', 'UA-'))) { - $requirements['googleanalytics'] = array( + if (!preg_match('/^UA-\d+-\d+$/', variable_get('googleanalytics_account', 'UA-'))) { + $requirements['googleanalytics_account'] = array( 'title' => $t('Google Analytics module'), 'description' => $t('Google Analytics module has not been configured yet. Please configure its settings from the Google Analytics settings page.', array('@url' => url('admin/config/system/googleanalytics'))), 'severity' => REQUIREMENT_WARNING, 'value' => $t('Not configured'), ); } + + // Raise warning if debugging is enabled. + if (variable_get('googleanalytics_debug', 0)) { + $requirements['google_analytics_debugging'] = array( + 'title' => $t('Google Analytics module'), + 'description' => $t('Google Analytics module has debugging enabled. Please disable debugging setting in production sites from the Google Analytics settings page.', array('@url' => url('admin/config/system/googleanalytics'))), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('Debugging enabled'), + ); + } } return $requirements; } /** - * Upgrade old extension variable to new and use old name as enabled/disabled flag. + * Upgrade old extension variable to new and use old name as enabled/disabled + * flag. */ function googleanalytics_update_6000() { variable_set('googleanalytics_trackfiles_extensions', variable_get('googleanalytics_trackfiles', '7z|aac|avi|csv|doc|exe|flv|gif|gz|jpe?g|js|mp(3|4|e?g)|mov|pdf|phps|png|ppt|rar|sit|tar|torrent|txt|wma|wmv|xls|xml|zip')); @@ -98,7 +103,8 @@ function googleanalytics_update_6000() { function googleanalytics_update_6001() { variable_set('googleanalytics_visibility', 0); - // Remove tracking from all administrative pages, see http://drupal.org/node/34970. + // Remove tracking from all administrative pages, see: + // https://drupal.org/node/34970. $pages = array( 'admin*', 'user*', @@ -111,11 +117,12 @@ function googleanalytics_update_6001() { } /** - * Upgrade role settings and per user tracking settings - * of "User 1" and remove outdated tracking variables. + * Upgrade role settings and per user tracking settings of "User 1" and remove + * outdated tracking variables. */ function googleanalytics_update_6002() { - // Upgrade enabled/disabled roles to new logic (correct for upgrades from 5.x-1.4 and 6.x-1.0). + // Upgrade enabled/disabled roles to new logic (correct for upgrades from + // 5.x-1.4 and 6.x-1.0). $roles = array(); $messages = array(); foreach (user_roles() as $rid => $name) { @@ -134,7 +141,7 @@ function googleanalytics_update_6002() { if (!$track_user1 = variable_get('googleanalytics_track__user1', 1)) { variable_set('googleanalytics_custom', 1); - // Load user 1 object, set appropriate value and save new user settings back. + // Load user 1 object, set appropriate value and save new user settings. $account = user_load(1); $account = user_save($account, array('data' => array('googleanalytics' => array('custom' => 0))), 'account'); $messages[] = t('Disabled user specific page tracking for site administrator.'); @@ -149,11 +156,11 @@ function googleanalytics_update_6002() { } /** - * #262468: Clear menu cache to solve stale menu data in 5.x-1.5 and 6.x-1.1 + * #262468: Clear menu cache to solve stale menu data in 5.x-1.5 and 6.x-1.1. */ function googleanalytics_update_6003() { menu_rebuild(); - return t('Menu has been rebuild.'); + return t('Menu has been rebuilt.'); } /** @@ -228,8 +235,9 @@ function googleanalytics_update_6006() { /** * Remove "User identifier" and "User name" from segmentation fields. * - * This is a data protection and privacy law change. For more information see Google Analytics - * terms of use section 8.1 (http://www.google.com/analytics/en-GB/tos.html). + * This is a data protection and privacy law change. For more information see + * Google Analytics terms of use section 8.1: + * https://www.google.com/analytics/en-GB/tos.html */ function googleanalytics_update_6007() { $profile_fields = variable_get('googleanalytics_segmentation', array()); @@ -290,15 +298,15 @@ function googleanalytics_update_6300() { $messages[] = t('Upgraded custom "before" code snippet.'); } - // Upgrade of AFTER code snippet. - // We cannot update this code snippet automatically. Show message that the upgrade has been skipped. + // Upgrade of AFTER code snippet. We cannot update this code snippet + // automatically. Show message that the upgrade has been skipped. $code_after = variable_get('googleanalytics_codesnippet_after', ''); if (!empty($code_after)) { drupal_set_message(Database::getConnection()->prefixTables("Automatic upgrade of Google Analytics custom 'after' code snippet has been skipped. Backup of previous code snippet has been saved in database table '{variable}' as 'googleanalytics_codesnippet_after_backup_6300'. You need to manually upgrade the custom 'after' code snippet."), 'error'); $messages[] = t('Skipped custom "after" code snippet.'); } - return empty($messages) ? t('No custom code snipped found. Nothing to do.') : implode(' ', $messages); + return empty($messages) ? t('No custom code snippet found. Nothing to do.') : implode(' ', $messages); } /** @@ -353,18 +361,20 @@ function googleanalytics_update_7002() { $googleanalytics_custom_vars['slots'][1]['slot'] = 1; $googleanalytics_custom_vars['slots'][1]['name'] = 'User roles'; $googleanalytics_custom_vars['slots'][1]['value'] = '[current-user:role-names]'; - $googleanalytics_custom_vars['slots'][1]['scope'] = 1; // Sets the scope to visitor-level. + // Sets the scope to visitor-level. + $googleanalytics_custom_vars['slots'][1]['scope'] = 1; variable_set('googleanalytics_custom_var', $googleanalytics_custom_vars); - return t('The deprecated profile segmentation setting for "User roles" has been added to custom variables. You need to deselect all selected profile fields in Google Analytics settings and upgrade other profile fields manually or you may loose tracking data in future! See Google Analytics Custom Variables for more information.', array('@customvar' => 'https://developers.google.com/analytics/devguides/collection/gajs/gaTrackingCustomVariables', '@admin' => url('admin/config/system/googleanalytics'))); + return t('The deprecated profile segmentation setting for "User roles" has been added to custom variables. You need to deselect all selected profile fields in Google Analytics settings and upgrade other profile fields manually or you may lose tracking data in future! See Google Analytics Custom Variables for more information.', array('@customvar' => 'https://developers.google.com/analytics/devguides/collection/gajs/gaTrackingCustomVariables', '@admin' => url('admin/config/system/googleanalytics'))); } else { - return t('You need to deselect all selected profile fields in Google Analytics settings and upgrade other profile fields manually or you may loose tracking data in future! See Google Analytics Custom Variables for more information.', array('@customvar' => 'https://developers.google.com/analytics/devguides/collection/gajs/gaTrackingCustomVariables', '@admin' => url('admin/config/system/googleanalytics'))); + return t('You need to deselect all selected profile fields in Google Analytics settings and upgrade other profile fields manually or you may lose tracking data in future! See Google Analytics Custom Variables for more information.', array('@customvar' => 'https://developers.google.com/analytics/devguides/collection/gajs/gaTrackingCustomVariables', '@admin' => url('admin/config/system/googleanalytics'))); } } /** - * Rename googleanalytics_trackoutgoing variable to googleanalytics_trackoutbound. + * Rename googleanalytics_trackoutgoing variable to + * googleanalytics_trackoutbound. */ function googleanalytics_update_7003() { variable_set('googleanalytics_trackoutbound', variable_get('googleanalytics_trackoutgoing', 1)); @@ -374,7 +384,8 @@ function googleanalytics_update_7003() { } /** - * Rename googleanalytics_visibility variable to googleanalytics_visibility_pages for consistency. + * Rename googleanalytics_visibility variable to + * googleanalytics_visibility_pages for consistency. */ function googleanalytics_update_7004() { variable_set('googleanalytics_visibility_pages', variable_get('googleanalytics_visibility', 1)); @@ -398,7 +409,7 @@ function googleanalytics_update_7005() { $diff = array_diff($pages, preg_split('/(\r\n?|\n)/', variable_get('googleanalytics_pages', implode("\n", $pages)))); if (empty($diff)) { - // No diff to previous settings found. Update with new settings. + // No difference to previous settings found. Update with new settings. $pages = array( 'admin', 'admin/*', @@ -425,10 +436,113 @@ function googleanalytics_update_7006() { } /** -* Delete obsolete googleanalytics_trackpageloadtime variable. -*/ + * Delete obsolete googleanalytics_trackpageloadtime variable. + */ function googleanalytics_update_7007() { variable_del('googleanalytics_trackpageloadtime'); return t('Deleted obsolete googleanalytics_trackpageloadtime variable.'); } + +/** + * Delete custom ga.js code snippets to prevent malfunctions in new Universal + * Analytics tracker. A backup of your snippets will be created. + */ +function googleanalytics_update_7200() { + $messages = array(); + + // ga.js code will cause the tracker to break. Remove custom code snippets. + $googleanalytics_codesnippet_before = variable_get('googleanalytics_codesnippet_before', ''); + if (!empty($googleanalytics_codesnippet_before) && stristr($googleanalytics_codesnippet_before, '_gaq.push(')) { + variable_set('googleanalytics_codesnippet_before_backup_7200', $googleanalytics_codesnippet_before); + variable_del('googleanalytics_codesnippet_before'); + drupal_set_message(Database::getConnection()->prefixTables("A backup of your previous Google Analytics code snippet (ga.js) has been saved in database table '{variable}' as 'googleanalytics_codesnippet_before_backup_7200'. You need to manually upgrade the custom 'before' code snippet to analytics.js API."), 'warning'); + $messages[] = t('Manual upgrade of custom "before" code snippet from ja.js to analytics.js API is required.'); + } + + $googleanalytics_codesnippet_after = variable_get('googleanalytics_codesnippet_after', ''); + if (!empty($googleanalytics_codesnippet_after) && stristr($googleanalytics_codesnippet_after, '_gaq.push(')) { + variable_set('googleanalytics_codesnippet_after_backup_7200', $googleanalytics_codesnippet_after); + variable_del('googleanalytics_codesnippet_after'); + drupal_set_message(Database::getConnection()->prefixTables("A backup of your previous Google Analytics code snippet (ga.js) has been saved in database table '{variable}' as 'googleanalytics_codesnippet_after_backup_7200'. You need to manually upgrade the custom 'after' code snippet to analytics.js API."), 'warning'); + $messages[] = t('Manual upgrade of custom "after" code snippet from ja.js to analytics.js API is required.'); + } + + return empty($messages) ? t('No custom code snippet found. Nothing to do.') : implode(' ', $messages); +} + +/** + * Delete obsolete custom variables. Custom variables are now custom dimensions + * and metrics. + */ +function googleanalytics_update_7201() { + variable_del('googleanalytics_custom_var'); + + return t('Deleted obsolete custom variables. Custom variables are now custom dimensions and metrics and you need to manually configure them!'); +} + +/** + * Delete obsolete JavaScript scope variable. + */ +function googleanalytics_update_7202() { + // Remove obsolete scope variable. + variable_del('googleanalytics_js_scope'); + + return t('Removed obsolete JavaScript scope variable.'); +} + +/** + * Flatten the metrics and dimensions arrays. + */ +function googleanalytics_update_7203() { + $googleanalytics_custom_dimension = variable_get('googleanalytics_custom_dimension', array()); + if (isset($googleanalytics_custom_dimension['indexes'])) { + foreach ($googleanalytics_custom_dimension['indexes'] as $dimension) { + $googleanalytics_custom_dimension['indexes'][$dimension['index']]['value'] = trim($dimension['value']); + // Remove empty values from the array. + if (!drupal_strlen($googleanalytics_custom_dimension['indexes'][$dimension['index']]['value'])) { + unset($googleanalytics_custom_dimension['indexes'][$dimension['index']]); + } + } + variable_set('googleanalytics_custom_dimension', $googleanalytics_custom_dimension['indexes']); + } + $googleanalytics_custom_metric = variable_get('googleanalytics_custom_metric', array()); + if (isset($googleanalytics_custom_metric['indexes'])) { + foreach ($googleanalytics_custom_metric['indexes'] as $dimension) { + $googleanalytics_custom_metric['indexes'][$dimension['index']]['value'] = trim($dimension['value']); + // Remove empty values from the array. + if (!drupal_strlen($googleanalytics_custom_metric['indexes'][$dimension['index']]['value'])) { + unset($googleanalytics_custom_metric['indexes'][$dimension['index']]); + } + } + variable_set('googleanalytics_custom_metric', $googleanalytics_custom_metric['indexes']); + } + + return t('Saved custom dimensions and metrics.'); +} + +/** + * Remove obsolete backup variables. + */ +function googleanalytics_update_7204() { + variable_del('googleanalytics_segmentation'); + variable_del('googleanalytics_codesnippet_after_backup_6300'); + variable_del('googleanalytics_codesnippet_before_backup_6300'); + variable_del('googleanalytics_codesnippet_after_backup_6400'); + variable_del('googleanalytics_codesnippet_before_backup_6400'); + + return t('Removed obsolete backup variables.'); +} + +/** + * Update list of default file extensions. + */ +function googleanalytics_update_7205() { + if (variable_get('googleanalytics_trackfiles_extensions', '') == '7z|aac|arc|arj|asf|asx|avi|bin|csv|doc|exe|flv|gif|gz|gzip|hqx|jar|jpe?g|js|mp(2|3|4|e?g)|mov(ie)?|msi|msp|pdf|phps|png|ppt|qtm?|ra(m|r)?|sea|sit|tar|tgz|torrent|txt|wav|wma|wmv|wpd|xls|xml|z|zip') { + variable_set('googleanalytics_trackfiles_extensions', '7z|aac|arc|arj|asf|asx|avi|bin|csv|doc(x|m)?|dot(x|m)?|exe|flv|gif|gz|gzip|hqx|jar|jpe?g|js|mp(2|3|4|e?g)|mov(ie)?|msi|msp|pdf|phps|png|ppt(x|m)?|pot(x|m)?|pps(x|m)?|ppam|sld(x|m)?|thmx|qtm?|ra(m|r)?|sea|sit|tar|tgz|torrent|txt|wav|wma|wmv|wpd|xls(x|m|b)?|xlt(x|m)|xlam|xml|z|zip'); + return t('The default extensions for download tracking have been updated.'); + } + else { + return t('Custom extensions for download tracking setting found. Update skipped!'); + } +} diff --git a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.js b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.js index 58be03d..5ba42ca 100644 --- a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.js +++ b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.js @@ -1,66 +1,93 @@ (function ($) { +Drupal.googleanalytics = {}; + $(document).ready(function() { - // Expression to check for absolute internal links. - var isInternal = new RegExp("^(https?):\/\/" + window.location.host, "i"); + // Attach mousedown, keyup, touchstart events to document only and catch + // clicks on all elements. + $(document.body).bind("mousedown keyup touchstart", function(event) { - // Attach onclick event to document only and catch clicks on all elements. - $(document.body).click(function(event) { // Catch the closest surrounding link of a clicked element. $(event.target).closest("a,area").each(function() { - var ga = Drupal.settings.googleanalytics; - // Expression to check for special links like gotwo.module /go/* links. - var isInternalSpecial = new RegExp("(\/go\/.*)$", "i"); - // Expression to check for download links. - var isDownload = new RegExp("\\.(" + ga.trackDownloadExtensions + ")$", "i"); - // Is the clicked URL internal? - if (isInternal.test(this.href)) { + if (Drupal.googleanalytics.isInternal(this.href)) { // Skip 'click' tracking, if custom tracking events are bound. - if ($(this).is('.colorbox')) { + if ($(this).is('.colorbox') && (Drupal.settings.googleanalytics.trackColorbox)) { // Do nothing here. The custom event will handle all tracking. + //console.info("Click on .colorbox item has been detected."); } // Is download tracking activated and the file extension configured for download tracking? - else if (ga.trackDownload && isDownload.test(this.href)) { + else if (Drupal.settings.googleanalytics.trackDownload && Drupal.googleanalytics.isDownload(this.href)) { // Download link clicked. - var extension = isDownload.exec(this.href); - _gaq.push(["_trackEvent", "Downloads", extension[1].toUpperCase(), this.href.replace(isInternal, '')]); + ga("send", { + "hitType": "event", + "eventCategory": "Downloads", + "eventAction": Drupal.googleanalytics.getDownloadExtension(this.href).toUpperCase(), + "eventLabel": Drupal.googleanalytics.getPageUrl(this.href), + "transport": "beacon" + }); } - else if (isInternalSpecial.test(this.href)) { + else if (Drupal.googleanalytics.isInternalSpecial(this.href)) { // Keep the internal URL for Google Analytics website overlay intact. - _gaq.push(["_trackPageview", this.href.replace(isInternal, '')]); + ga("send", { + "hitType": "pageview", + "page": Drupal.googleanalytics.getPageUrl(this.href), + "transport": "beacon" + }); } } else { - if (ga.trackMailto && $(this).is("a[href^='mailto:'],area[href^='mailto:']")) { + if (Drupal.settings.googleanalytics.trackMailto && $(this).is("a[href^='mailto:'],area[href^='mailto:']")) { // Mailto link clicked. - _gaq.push(["_trackEvent", "Mails", "Click", this.href.substring(7)]); + ga("send", { + "hitType": "event", + "eventCategory": "Mails", + "eventAction": "Click", + "eventLabel": this.href.substring(7), + "transport": "beacon" + }); } - else if (ga.trackOutbound && this.href.match(/^\w+:\/\//i)) { - if (ga.trackDomainMode == 2 && isCrossDomain($(this).attr('hostname'), ga.trackCrossDomains)) { - // Top-level cross domain clicked. document.location is handled by _link internally. - event.preventDefault(); - _gaq.push(["_link", this.href]); - } - else { - // External link clicked. - _gaq.push(["_trackEvent", "Outbound links", "Click", this.href]); + else if (Drupal.settings.googleanalytics.trackOutbound && this.href.match(/^\w+:\/\//i)) { + if (Drupal.settings.googleanalytics.trackDomainMode !== 2 || (Drupal.settings.googleanalytics.trackDomainMode === 2 && !Drupal.googleanalytics.isCrossDomain(this.hostname, Drupal.settings.googleanalytics.trackCrossDomains))) { + // External link clicked / No top-level cross domain clicked. + ga("send", { + "hitType": "event", + "eventCategory": "Outbound links", + "eventAction": "Click", + "eventLabel": this.href, + "transport": "beacon" + }); } } } }); }); + // Track hash changes as unique pageviews, if this option has been enabled. + if (Drupal.settings.googleanalytics.trackUrlFragments) { + window.onhashchange = function() { + ga("send", { + "hitType": "pageview", + "page": location.pathname + location.search + location.hash + }); + }; + } + // Colorbox: This event triggers when the transition has completed and the // newly loaded content has been revealed. - $(document).bind("cbox_complete", function() { - var href = $.colorbox.element().attr("href"); - if (href) { - _gaq.push(["_trackPageview", href.replace(isInternal, '')]); - } - }); + if (Drupal.settings.googleanalytics.trackColorbox) { + $(document).bind("cbox_complete", function () { + var href = $.colorbox.element().attr("href"); + if (href) { + ga("send", { + "hitType": "pageview", + "page": Drupal.googleanalytics.getPageUrl(href) + }); + } + }); + } }); @@ -74,10 +101,10 @@ $(document).ready(function() { * * @return boolean */ -function isCrossDomain(hostname, crossDomains) { +Drupal.googleanalytics.isCrossDomain = function (hostname, crossDomains) { /** * jQuery < 1.6.3 bug: $.inArray crushes IE6 and Chrome if second argument is - * `null` or `undefined`, http://bugs.jquery.com/ticket/10076, + * `null` or `undefined`, https://bugs.jquery.com/ticket/10076, * https://github.com/jquery/jquery/commit/a839af034db2bd934e4d4fa6758a3fed8de74174 * * @todo: Remove/Refactor in D8 @@ -88,6 +115,81 @@ function isCrossDomain(hostname, crossDomains) { else { return $.inArray(hostname, crossDomains) > -1 ? true : false; } -} +}; + +/** + * Check whether this is a download URL or not. + * + * @param string url + * The web url to check. + * + * @return boolean + */ +Drupal.googleanalytics.isDownload = function (url) { + var isDownload = new RegExp("\\.(" + Drupal.settings.googleanalytics.trackDownloadExtensions + ")([\?#].*)?$", "i"); + return isDownload.test(url); +}; + +/** + * Check whether this is an absolute internal URL or not. + * + * @param string url + * The web url to check. + * + * @return boolean + */ +Drupal.googleanalytics.isInternal = function (url) { + var isInternal = new RegExp("^(https?):\/\/" + window.location.host, "i"); + return isInternal.test(url); +}; + +/** + * Check whether this is a special URL or not. + * + * URL types: + * - gotwo.module /go/* links. + * + * @param string url + * The web url to check. + * + * @return boolean + */ +Drupal.googleanalytics.isInternalSpecial = function (url) { + var isInternalSpecial = new RegExp("(\/go\/.*)$", "i"); + return isInternalSpecial.test(url); +}; + +/** + * Extract the relative internal URL from an absolute internal URL. + * + * Examples: + * - https://mydomain.com/node/1 -> /node/1 + * - https://example.com/foo/bar -> https://example.com/foo/bar + * + * @param string url + * The web url to check. + * + * @return string + * Internal website URL + */ +Drupal.googleanalytics.getPageUrl = function (url) { + var extractInternalUrl = new RegExp("^(https?):\/\/" + window.location.host, "i"); + return url.replace(extractInternalUrl, ''); +}; + +/** + * Extract the download file extension from the URL. + * + * @param string url + * The web url to check. + * + * @return string + * The file extension of the passed url. e.g. "zip", "txt" + */ +Drupal.googleanalytics.getDownloadExtension = function (url) { + var extractDownloadextension = new RegExp("\\.(" + Drupal.settings.googleanalytics.trackDownloadExtensions + ")([\?#].*)?$", "i"); + var extension = extractDownloadextension.exec(url); + return (extension === null) ? '' : extension[1]; +}; })(jQuery); diff --git a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.module b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.module index 9efae87..0026703 100644 --- a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.module +++ b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.module @@ -1,26 +1,42 @@ + * Adds the required Javascript to all your Drupal pages to allow tracking by + * the Google Analytics statistics package. + * + * @author: Alexander Hass */ -define('GOOGLEANALYTICS_TRACKFILES_EXTENSIONS', '7z|aac|arc|arj|asf|asx|avi|bin|csv|doc|exe|flv|gif|gz|gzip|hqx|jar|jpe?g|js|mp(2|3|4|e?g)|mov(ie)?|msi|msp|pdf|phps|png|ppt|qtm?|ra(m|r)?|sea|sit|tar|tgz|torrent|txt|wav|wma|wmv|wpd|xls|xml|z|zip'); +/** + * Define the default file extension list that should be tracked as download. + */ +define('GOOGLEANALYTICS_TRACKFILES_EXTENSIONS', '7z|aac|arc|arj|asf|asx|avi|bin|csv|doc(x|m)?|dot(x|m)?|exe|flv|gif|gz|gzip|hqx|jar|jpe?g|js|mp(2|3|4|e?g)|mov(ie)?|msi|msp|pdf|phps|png|ppt(x|m)?|pot(x|m)?|pps(x|m)?|ppam|sld(x|m)?|thmx|qtm?|ra(m|r)?|sea|sit|tar|tgz|torrent|txt|wav|wma|wmv|wpd|xls(x|m|b)?|xlt(x|m)|xlam|xml|z|zip'); -// Remove tracking from all administrative pages, see http://drupal.org/node/34970. +/** + * Define default path exclusion list to remove tracking from admin pages, + * see https://drupal.org/node/34970 for more information. + */ define('GOOGLEANALYTICS_PAGES', "admin\nadmin/*\nbatch\nnode/add*\nnode/*/*\nuser/*/*"); +/** + * Advertise the supported google analytics api details. + */ +function googleanalytics_api() { + return array( + 'api' => 'analytics.js', + ); +} + /** * Implements hook_help(). */ function googleanalytics_help($path, $arg) { switch ($path) { case 'admin/config/system/googleanalytics': - return t('Google Analytics is a free (registration required) website traffic and marketing effectiveness service.', array('@ga_url' => 'http://www.google.com/analytics/')); + return t('Google Analytics is a free (registration required) website traffic and marketing effectiveness service.', array('@ga_url' => 'https://marketingplatform.google.com/about/analytics/')); } } @@ -53,6 +69,11 @@ function googleanalytics_permission() { 'description' => t('Enter PHP code in the field for tracking visibility settings.'), 'restrict access' => TRUE, ), + 'add JS snippets for google analytics' => array( + 'title' => t('Add JavaScript snippets'), + 'description' => 'Enter JavaScript code snippets for advanced Google Analytics functionality.', + 'restrict access' => TRUE, + ), ); } @@ -77,7 +98,7 @@ function googleanalytics_menu() { * Implements hook_page_alter() to insert JavaScript to the appropriate scope/region of the page. */ function googleanalytics_page_alter(&$page) { - global $user; + global $base_path, $user; $id = variable_get('googleanalytics_account', ''); @@ -88,19 +109,14 @@ function googleanalytics_page_alter(&$page) { '404 Not Found', ); - // 1. Check if the GA account number has a value. + // 1. Check if the GA account number has a valid value. // 2. Track page views based on visibility value. // 3. Check if we should track the currently active user's role. // 4. Ignore pages visibility filter for 404 or 403 status codes. - if (!empty($id) && (_googleanalytics_visibility_pages() || in_array($status, $trackable_status_codes)) && _googleanalytics_visibility_user($user)) { + if (preg_match('/^UA-\d+-\d+$/', $id) && (_googleanalytics_visibility_pages() || in_array($status, $trackable_status_codes)) && _googleanalytics_visibility_user($user)) { - // We allow different scopes. Default to 'header' but allow user to override if they really need to. - $scope = variable_get('googleanalytics_js_scope', 'header'); - - if (variable_get('googleanalytics_trackadsense', FALSE)) { - // Custom tracking. Prepend before all other JavaScript. - drupal_add_js('window.google_analytics_uacct = ' . drupal_json_encode($id) . ';', array('type' => 'inline', 'group' => JS_LIBRARY-1)); - } + $debug = variable_get('googleanalytics_debug', 0); + $url_custom = ''; // Add link tracking. $link_settings = array(); @@ -114,16 +130,32 @@ function googleanalytics_page_alter(&$page) { $link_settings['trackDownload'] = $track_download; $link_settings['trackDownloadExtensions'] = $trackfiles_extensions; } + if (module_exists('colorbox') && ($track_colorbox = variable_get('googleanalytics_trackcolorbox', 1))) { + $link_settings['trackColorbox'] = $track_colorbox; + } if ($track_domain_mode = variable_get('googleanalytics_domain_mode', 0)) { - $link_settings['trackDomainMode'] = $track_domain_mode; + $link_settings['trackDomainMode'] = (int) $track_domain_mode; } if ($track_cross_domains = variable_get('googleanalytics_cross_domains', '')) { $link_settings['trackCrossDomains'] = preg_split('/(\r\n?|\n)/', $track_cross_domains); } + if ($track_url_fragments = variable_get('googleanalytics_trackurlfragments', 0)) { + $link_settings['trackUrlFragments'] = $track_url_fragments; + $url_custom = 'location.pathname + location.search + location.hash'; + } if (!empty($link_settings)) { drupal_add_js(array('googleanalytics' => $link_settings), 'setting'); - drupal_add_js(drupal_get_path('module', 'googleanalytics') . '/googleanalytics.js'); + + // Add debugging code. + if ($debug) { + drupal_add_js(drupal_get_path('module', 'googleanalytics') . '/googleanalytics.debug.js'); + // Add the JS test in development to the page. + //drupal_add_js(drupal_get_path('module', 'googleanalytics') . '/googleanalytics.test.js'); + } + else { + drupal_add_js(drupal_get_path('module', 'googleanalytics') . '/googleanalytics.js'); + } } // Add messages tracking. @@ -140,15 +172,17 @@ function googleanalytics_page_alter(&$page) { // Track only the selected message types. if (in_array($type, $message_types)) { foreach ($messages as $message) { - $message_events .= '_gaq.push(["_trackEvent", ' . drupal_json_encode(t('Messages')) . ', ' . drupal_json_encode($status_heading[$type]) . ', ' . drupal_json_encode(strip_tags($message)) . ']);'; + // @todo: Track as exceptions? + $message_events .= 'ga("send", "event", ' . drupal_json_encode(t('Messages')) . ', ' . drupal_json_encode($status_heading[$type]) . ', ' . drupal_json_encode(strip_tags($message)) . ');'; } } } } // Site search tracking support. - $url_custom = ''; if (module_exists('search') && variable_get('googleanalytics_site_search', FALSE) && arg(0) == 'search' && $keys = googleanalytics_search_get_keys()) { + // hook_preprocess_search_results() is not executed if search result is + // empty. Make sure the counter is set to 0 if there are no results. $url_custom = '(window.googleanalytics_search_results) ? ' . drupal_json_encode(url('search/' . arg(1), array('query' => array('search' => $keys)))) . ' : ' . drupal_json_encode(url('search/' . arg(1), array('query' => array('search' => 'no-results:' . $keys, 'cat' => 'no-results')))); } @@ -167,77 +201,149 @@ function googleanalytics_page_alter(&$page) { // Track access denied (403) and file not found (404) pages. if ($status == '403 Forbidden') { - // See http://www.google.com/support/analytics/bin/answer.py?answer=86927 - $url_custom = '"/403.html?page=" + document.location.pathname + document.location.search + "&from=" + document.referrer'; + // See https://www.google.com/support/analytics/bin/answer.py?answer=86927 + $url_custom = '"' . $base_path . '403.html?page=" + document.location.pathname + document.location.search + "&from=" + document.referrer'; } elseif ($status == '404 Not Found') { - $url_custom = '"/404.html?page=" + document.location.pathname + document.location.search + "&from=" + document.referrer'; + $url_custom = '"' . $base_path . '404.html?page=" + document.location.pathname + document.location.search + "&from=" + document.referrer'; } - // Add any custom code snippets if specified. - $codesnippet_before = variable_get('googleanalytics_codesnippet_before', ''); - $codesnippet_after = variable_get('googleanalytics_codesnippet_after', ''); + // #2693595: User has entered an invalid login and clicked on forgot + // password link. This link contains the username or email address and may + // get send to Google if we do not override it. Override only if 'name' + // query param exists. Last custom url condition, this need to win. + // + // URLs to protect are: + // - user/password?name=username + // - user/password?name=foo@example.com + if (arg(0) == 'user' && arg(1) == 'password' && array_key_exists('name', drupal_get_query_parameters())) { + $url_custom = '"' . $base_path . 'user/password"'; + } - // Add custom variables. - $googleanalytics_custom_vars = variable_get('googleanalytics_custom_var', array()); + // Add custom dimensions and metrics. $custom_var = ''; - for ($i = 1; $i < 6; $i++) { - $custom_var_name = !empty($googleanalytics_custom_vars['slots'][$i]['name']) ? $googleanalytics_custom_vars['slots'][$i]['name'] : ''; - if (!empty($custom_var_name)) { - $custom_var_value = !empty($googleanalytics_custom_vars['slots'][$i]['value']) ? $googleanalytics_custom_vars['slots'][$i]['value'] : ''; - $custom_var_scope = !empty($googleanalytics_custom_vars['slots'][$i]['scope']) ? $googleanalytics_custom_vars['slots'][$i]['scope'] : 3; + foreach (array('dimension', 'metric') as $googleanalytics_custom_type) { + $googleanalytics_custom_vars = variable_get('googleanalytics_custom_' . $googleanalytics_custom_type, array()); + // Are there dimensions or metrics configured? + if (!empty($googleanalytics_custom_vars)) { + // Add all the configured variables to the content. + foreach ($googleanalytics_custom_vars as $googleanalytics_custom_var) { + // Replace tokens in values. + $types = array(); + $node = menu_get_object(); + if (is_object($node)) { + $types += array('node' => $node); + } + $googleanalytics_custom_var['value'] = token_replace($googleanalytics_custom_var['value'], $types, array('clear' => TRUE)); - $types = array(); - $node = menu_get_object(); - if (is_object($node)) { - $types += array('node' => $node); + // Suppress empty values. + if (!drupal_strlen(trim($googleanalytics_custom_var['value']))) { + continue; + } + + // Per documentation the max length of a dimension is 150 bytes. + // A metric has no length limitation. It's not documented if this + // limit means 150 bytes after url encoding or before. + // See https://developers.google.com/analytics/devguides/collection/analyticsjs/field-reference#customs + if ($googleanalytics_custom_type == 'dimension' && drupal_strlen($googleanalytics_custom_var['value']) > 150) { + $googleanalytics_custom_var['value'] = substr($googleanalytics_custom_var['value'], 0, 150); + } + + // Cast metric values for json_encode to data type numeric. + if ($googleanalytics_custom_type == 'metric') { + settype($googleanalytics_custom_var['value'], 'float'); + }; + + // Add variables to tracker. + $custom_var .= 'ga("set", ' . drupal_json_encode($googleanalytics_custom_type . $googleanalytics_custom_var['index']) . ', ' . drupal_json_encode($googleanalytics_custom_var['value']) . ');'; } - $custom_var_name = token_replace($custom_var_name, $types, array('clear' => TRUE)); - $custom_var_value = token_replace($custom_var_value, $types, array('clear' => TRUE)); - - // Suppress empty custom names and/or variables. - if (!drupal_strlen(trim($custom_var_name)) || !drupal_strlen(trim($custom_var_value))) { - continue; - } - - // The length of the string used for the 'name' and the length of the - // string used for the 'value' must not exceed 128 bytes after url encoding. - $name_length = drupal_strlen(rawurlencode($custom_var_name)); - $tmp_value = rawurlencode($custom_var_value); - $value_length = drupal_strlen($tmp_value); - if ($name_length + $value_length > 128) { - // Trim value and remove fragments of url encoding. - $tmp_value = rtrim(substr($tmp_value, 0, 127 - $name_length), '%0..9A..F'); - $custom_var_value = urldecode($tmp_value); - } - - $custom_var_name = drupal_json_encode($custom_var_name); - $custom_var_value = drupal_json_encode($custom_var_value); - $custom_var .= "_gaq.push(['_setCustomVar', $i, $custom_var_name, $custom_var_value, $custom_var_scope]);"; } } // Build tracker code. - $script = 'var _gaq = _gaq || [];'; - $script .= '_gaq.push(["_setAccount", ' . drupal_json_encode($id) . ']);'; - if (variable_get('googleanalytics_tracker_anonymizeip', 0)) { - // FIXME: The Google API is currently broken and "_gat._anonymizeIp" is only - // a workaround until "_anonymizeIp" has been implemented/fixed. - $script .= '_gaq.push(["_gat._anonymizeIp"]);'; + $script = '(function(i,s,o,g,r,a,m){'; + $script .= 'i["GoogleAnalyticsObject"]=r;i[r]=i[r]||function(){'; + $script .= '(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),'; + $script .= 'm=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)'; + $script .= '})(window,document,"script",'; + + // Which version of the tracking library should be used? + $library_tracker_url = 'https://www.google-analytics.com/' . ($debug ? 'analytics_debug.js' : 'analytics.js'); + + // Should a local cached copy of analytics.js be used? + if (variable_get('googleanalytics_cache', 0) && $url = _googleanalytics_cache($library_tracker_url)) { + // A dummy query-string is added to filenames, to gain control over + // browser-caching. The string changes on every update or full cache + // flush, forcing browsers to load a new copy of the files, as the + // URL changed. + $query_string = '?' . variable_get('css_js_query_string', '0'); + + $script .= '"' . $url . $query_string . '"'; } + else { + $script .= '"' . $library_tracker_url . '"'; + } + $script .= ',"ga");'; + + // Add any custom code snippets if specified. + $codesnippet_create = variable_get('googleanalytics_codesnippet_create', array()); + $codesnippet_before = variable_get('googleanalytics_codesnippet_before', ''); + $codesnippet_after = variable_get('googleanalytics_codesnippet_after', ''); + + // Build the create only fields list. + $create_only_fields = array('cookieDomain' => 'auto'); + $create_only_fields = array_merge($create_only_fields, $codesnippet_create); // Domain tracking type. global $cookie_domain; $domain_mode = variable_get('googleanalytics_domain_mode', 0); + $googleanalytics_adsense_script = ''; // Per RFC 2109, cookie domains must contain at least one dot other than the // first. For hosts such as 'localhost' or IP Addresses we don't set a cookie domain. if ($domain_mode == 1 && count(explode('.', $cookie_domain)) > 2 && !is_numeric(str_replace('.', '', $cookie_domain))) { - $script .= '_gaq.push(["_setDomainName", ' . drupal_json_encode($cookie_domain) . ']);'; + $create_only_fields = array_merge($create_only_fields, array('cookieDomain' => $cookie_domain)); + $googleanalytics_adsense_script .= 'window.google_analytics_domain_name = ' . drupal_json_encode($cookie_domain) . ';'; } elseif ($domain_mode == 2) { - $script .= '_gaq.push(["_setDomainName", "none"]);'; - $script .= '_gaq.push(["_setAllowLinker", true]);'; + // Cross Domain tracking. 'autoLinker' need to be enabled in 'create'. + $create_only_fields = array_merge($create_only_fields, array('allowLinker' => TRUE)); + $googleanalytics_adsense_script .= 'window.google_analytics_domain_name = "none";'; + } + + // Track logged in users across all devices. + if (variable_get('googleanalytics_trackuserid', 0) && user_is_logged_in()) { + $create_only_fields['userId'] = google_analytics_user_id_hash($user->uid); + } + + // Create a tracker. + $script .= 'ga("create", ' . drupal_json_encode($id) . ', ' . drupal_json_encode($create_only_fields) .');'; + + // Prepare Adsense tracking. + $googleanalytics_adsense_script .= 'window.google_analytics_uacct = ' . drupal_json_encode($id) . ';'; + + // Add enhanced link attribution after 'create', but before 'pageview' send. + // @see https://support.google.com/analytics/answer/2558867 + if (variable_get('googleanalytics_tracklinkid', 0)) { + $script .= 'ga("require", "linkid", "linkid.js");'; + } + + // Add display features after 'create', but before 'pageview' send. + // @see https://support.google.com/analytics/answer/2444872 + if (variable_get('googleanalytics_trackdoubleclick', 0)) { + $script .= 'ga("require", "displayfeatures");'; + } + + // Domain tracking type. + if ($domain_mode == 2) { + // Cross Domain tracking + // https://developers.google.com/analytics/devguides/collection/upgrade/reference/gajs-analyticsjs#cross-domain + $script .= 'ga("require", "linker");'; + $script .= 'ga("linker:autoLink", ' . drupal_json_encode($link_settings['trackCrossDomains']) . ');'; + } + + if (variable_get('googleanalytics_tracker_anonymizeip', 1)) { + $script .= 'ga("set", "anonymizeIp", true);'; } if (!empty($custom_var)) { @@ -246,12 +352,11 @@ function googleanalytics_page_alter(&$page) { if (!empty($codesnippet_before)) { $script .= $codesnippet_before; } - if (empty($url_custom)) { - $script .= '_gaq.push(["_trackPageview"]);'; - } - else { - $script .= '_gaq.push(["_trackPageview", ' . $url_custom . ']);'; + if (!empty($url_custom)) { + $script .= 'ga("set", "page", ' . $url_custom . ');'; } + $script .= 'ga("send", "pageview");'; + if (!empty($message_events)) { $script .= $message_events; } @@ -259,48 +364,34 @@ function googleanalytics_page_alter(&$page) { $script .= $codesnippet_after; } - $script .= '(function() {'; - $script .= 'var ga = document.createElement("script");'; - $script .= 'ga.type = "text/javascript";'; - $script .= 'ga.async = true;'; - - // Which version of the tracking library should be used? - if ($trackdoubleclick = variable_get('googleanalytics_trackdoubleclick', FALSE)) { - $library_tracker_url = 'stats.g.doubleclick.net/dc.js'; - $library_cache_url = 'http://' . $library_tracker_url; - } - else { - $library_tracker_url = '.google-analytics.com/ga.js'; - $library_cache_url = 'http://www' . $library_tracker_url; + if (variable_get('googleanalytics_trackadsense', FALSE)) { + // Custom tracking. Prepend before all other JavaScript. + // @TODO: https://support.google.com/adsense/answer/98142 + // sounds like it could be appended to $script. + drupal_add_js($googleanalytics_adsense_script, array('type' => 'inline', 'group' => JS_LIBRARY-1, 'requires_jquery' => FALSE)); } - // Should a local cached copy of ga.js be used? - if (variable_get('googleanalytics_cache', 0) && $url = _googleanalytics_cache($library_cache_url)) { - // A dummy query-string is added to filenames, to gain control over - // browser-caching. The string changes on every update or full cache - // flush, forcing browsers to load a new copy of the files, as the - // URL changed. - $query_string = '?' . variable_get('css_js_query_string', '0'); - - $script .= 'ga.src = "' . $url . $query_string . '";'; - } - else { - // Library paths do not follow the same naming convention. - if ($trackdoubleclick) { - $script .= 'ga.src = ("https:" == document.location.protocol ? "https://" : "http://") + "' . $library_tracker_url . '";'; - } - else { - $script .= 'ga.src = ("https:" == document.location.protocol ? "https://ssl" : "http://www") + "' . $library_tracker_url . '";'; - } - } - $script .= 'var s = document.getElementsByTagName("script")[0];'; - $script .= 's.parentNode.insertBefore(ga, s);'; - $script .= '})();'; - - drupal_add_js($script, array('scope' => $scope, 'type' => 'inline')); + drupal_add_js($script, array('scope' => 'header', 'type' => 'inline', 'requires_jquery' => FALSE)); } } +/** + * Generate user id hash to implement USER_ID. + * + * The USER_ID value should be a unique, persistent, and non-personally + * identifiable string identifier that represents a user or signed-in + * account across devices. + * + * @param int $uid + * User id. + * + * @return string + * User id hash. + */ +function google_analytics_user_id_hash($uid) { + return drupal_hmac_base64($uid, drupal_get_private_key() . drupal_get_hash_salt()); +} + /** * Implements hook_field_extra_fields(). */ @@ -382,13 +473,7 @@ function googleanalytics_user_presave(&$edit, $account, $category) { function googleanalytics_cron() { // Regenerate the tracking code file every day. if (REQUEST_TIME - variable_get('googleanalytics_last_cache', 0) >= 86400 && variable_get('googleanalytics_cache', 0)) { - // Which version of the tracking library should be used? - if (variable_get('googleanalytics_trackdoubleclick', FALSE)) { - _googleanalytics_cache('http://stats.g.doubleclick.net/dc.js', TRUE); - } - else { - _googleanalytics_cache('http://www.google-analytics.com/ga.js', TRUE); - } + _googleanalytics_cache('https://www.google-analytics.com/analytics.js', TRUE); variable_set('googleanalytics_last_cache', REQUEST_TIME); } } @@ -399,17 +484,19 @@ function googleanalytics_cron() { * Collects and adds the number of search results to the head. */ function googleanalytics_preprocess_search_results(&$variables) { - // There is no search result $variable available that hold the number of items - // found. But the pager item mumber can tell the number of search results. - global $pager_total_items; + if (variable_get('googleanalytics_site_search', FALSE)) { + // There is no search result $variable available that hold the number of items + // found. But the pager item mumber can tell the number of search results. + global $pager_total_items; - drupal_add_js('window.googleanalytics_search_results = ' . intval($pager_total_items[0]) . ';', array('type' => 'inline', 'group' => JS_LIBRARY-1)); + drupal_add_js('window.googleanalytics_search_results = ' . intval($pager_total_items[0]) . ';', array('type' => 'inline', 'group' => JS_LIBRARY-1, 'requires_jquery' => FALSE)); + } } /** * Helper function for grabbing search keys. Function is missing in D7. * - * http://api.drupal.org/api/function/search_get_keys/6 + * https://api.drupal.org/api/function/search_get_keys/6 */ function googleanalytics_search_get_keys() { static $return; @@ -428,16 +515,16 @@ function googleanalytics_search_get_keys() { * * @param $location * The full URL to the external javascript file. - * @param $sync_cached_file - * Synchronize tracking code and update if remote file have changed. + * @param $synchronize + * Synchronize to local cache if remote file has changed. * @return mixed * The path to the local javascript file on success, boolean FALSE on failure. */ -function _googleanalytics_cache($location, $sync_cached_file = FALSE) { +function _googleanalytics_cache($location, $synchronize = FALSE) { $path = 'public://googleanalytics'; $file_destination = $path . '/' . basename($location); - if (!file_exists($file_destination) || $sync_cached_file) { + if (!file_exists($file_destination) || $synchronize) { // Download the latest tracking code. $result = drupal_http_request($location); @@ -450,6 +537,10 @@ function _googleanalytics_cache($location, $sync_cached_file = FALSE) { if ($data_hash_local != $data_hash_remote && file_prepare_directory($path)) { // Save updated tracking code file to disk. file_unmanaged_save_data($result->data, $file_destination, FILE_EXISTS_REPLACE); + // Based on Drupal Core drupal_build_css_cache(). + if (variable_get('css_gzip_compression', TRUE) && variable_get('clean_url', 0) && extension_loaded('zlib')) { + file_unmanaged_save_data(gzencode($result->data, 9, FORCE_GZIP), $file_destination . '.gz', FILE_EXISTS_REPLACE); + } watchdog('googleanalytics', 'Locally cached tracking code file has been updated.', array(), WATCHDOG_INFO); // Change query-strings on css/js files to enforce reload for all users. @@ -462,6 +553,10 @@ function _googleanalytics_cache($location, $sync_cached_file = FALSE) { // There is no need to flush JS here as core refreshes JS caches // automatically, if new files are added. file_unmanaged_save_data($result->data, $file_destination, FILE_EXISTS_REPLACE); + // Based on Drupal Core drupal_build_css_cache(). + if (variable_get('css_gzip_compression', TRUE) && variable_get('clean_url', 0) && extension_loaded('zlib')) { + file_unmanaged_save_data(gzencode($result->data, 9, FORCE_GZIP), $file_destination . '.gz', FILE_EXISTS_REPLACE); + } watchdog('googleanalytics', 'Locally cached tracking code file has been saved.', array(), WATCHDOG_INFO); // Return the local JS file path. diff --git a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.test b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.test index f8a68cd..52c3148 100644 --- a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.test +++ b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.test @@ -6,10 +6,17 @@ */ class GoogleAnalyticsBasicTest extends DrupalWebTestCase { + /** + * User without permissions to edit snippets. + * + * @var \StdClass + */ + protected $noSnippetUser; + public static function getInfo() { return array( - 'name' => t('Google Analytics basic tests'), - 'description' => t('Test basic functionality of Google Analytics module.'), + 'name' => 'Google Analytics basic tests', + 'description' => 'Test basic functionality of Google Analytics module.', 'group' => 'Google Analytics', ); } @@ -20,31 +27,70 @@ class GoogleAnalyticsBasicTest extends DrupalWebTestCase { $permissions = array( 'access administration pages', 'administer google analytics', + 'administer modules', + 'administer site configuration', ); // User to set up google_analytics. + $this->noSnippetUser = $this->drupalCreateUser($permissions); + $permissions[] = 'add JS snippets for google analytics'; $this->admin_user = $this->drupalCreateUser($permissions); $this->drupalLogin($this->admin_user); } function testGoogleAnalyticsConfiguration() { + // Check if Configure link is available on 'Modules' page. + // Requires 'administer modules' permission. + $this->drupalGet('admin/modules'); + $this->assertRaw('admin/config/system/googleanalytics', '[testGoogleAnalyticsConfiguration]: Configure link from Modules page to Google Analytics Settings page exists.'); + + // Check if Configure link is available on 'Status Reports' page. NOTE: Link is only shown without UA code configured. + // Requires 'administer site configuration' permission. + $this->drupalGet('admin/reports/status'); + $this->assertRaw('admin/config/system/googleanalytics', '[testGoogleAnalyticsConfiguration]: Configure link from Status Reports page to Google Analytics Settings page exists.'); + // Check for setting page's presence. $this->drupalGet('admin/config/system/googleanalytics'); $this->assertRaw(t('Web Property ID'), '[testGoogleAnalyticsConfiguration]: Settings page displayed.'); // Check for account code validation. $edit['googleanalytics_account'] = $this->randomName(2); - $this->drupalPost('admin/config/system/googleanalytics', $edit, 'Save configuration'); + $this->drupalPost('admin/config/system/googleanalytics', $edit, t('Save configuration')); $this->assertRaw(t('A valid Google Analytics Web Property ID is case sensitive and formatted like UA-xxxxxxx-yy.'), '[testGoogleAnalyticsConfiguration]: Invalid Web Property ID number validated.'); + + // User should have access to code snippets. + $this->assertFieldByName('googleanalytics_codesnippet_create'); + $this->assertFieldByName('googleanalytics_codesnippet_before'); + $this->assertFieldByName('googleanalytics_codesnippet_after'); + $this->assertNoFieldByXPath("//textarea[@name='googleanalytics_codesnippet_create' and @disabled='disabled']", NULL, '"Create only fields" is enabled.'); + $this->assertNoFieldByXPath("//textarea[@name='googleanalytics_codesnippet_before' and @disabled='disabled']", NULL, '"Code snippet (before)" is enabled.'); + $this->assertNoFieldByXPath("//textarea[@name='googleanalytics_codesnippet_after' and @disabled='disabled']", NULL, '"Code snippet (after)" is enabled.'); + + // Login as user without JS permissions. + $this->drupalLogin($this->noSnippetUser); + $this->drupalGet('admin/config/system/googleanalytics'); + + // User should *not* have access to snippets, but create fields. + $this->assertFieldByName('googleanalytics_codesnippet_create'); + $this->assertFieldByName('googleanalytics_codesnippet_before'); + $this->assertFieldByName('googleanalytics_codesnippet_after'); + $this->assertNoFieldByXPath("//textarea[@name='googleanalytics_codesnippet_create' and @disabled='disabled']", NULL, '"Create only fields" is enabled.'); + $this->assertFieldByXPath("//textarea[@name='googleanalytics_codesnippet_before' and @disabled='disabled']", NULL, '"Code snippet (before)" is disabled.'); + $this->assertFieldByXPath("//textarea[@name='googleanalytics_codesnippet_after' and @disabled='disabled']", NULL, '"Code snippet (after)" is disabled.'); } function testGoogleAnalyticsPageVisibility() { + // Verify that no tracking code is embedded into the webpage; if there is + // only the module installed, but UA code not configured. See #2246991. + $this->drupalGet(''); + $this->assertNoRaw('https://www.google-analytics.com/analytics.js', '[testGoogleAnalyticsPageVisibility]: Tracking code is not displayed without UA code configured.'); + $ua_code = 'UA-123456-1'; variable_set('googleanalytics_account', $ua_code); // Show tracking on "every page except the listed pages". variable_set('googleanalytics_visibility_pages', 0); - // Disable tracking one "admin*" pages only. + // Disable tracking on "admin*" pages only. variable_set('googleanalytics_pages', "admin\nadmin/*"); // Enable tracking only for authenticated users only. variable_set('googleanalytics_roles', array(DRUPAL_AUTHENTICATED_RID => DRUPAL_AUTHENTICATED_RID)); @@ -58,7 +104,7 @@ class GoogleAnalyticsBasicTest extends DrupalWebTestCase { $this->assertNoRaw($ua_code, '[testGoogleAnalyticsPageVisibility]: Tracking code is not displayed on admin page.'); $this->drupalGet('admin/config/system/googleanalytics'); // Checking for tracking code URI here, as $ua_code is displayed in the form. - $this->assertNoRaw('google-analytics.com/ga.js', '[testGoogleAnalyticsPageVisibility]: Tracking code is not displayed on admin subpage.'); + $this->assertNoRaw('https://www.google-analytics.com/analytics.js', '[testGoogleAnalyticsPageVisibility]: Tracking code is not displayed on admin subpage.'); // Test whether tracking code display is properly flipped. variable_set('googleanalytics_visibility_pages', 1); @@ -66,7 +112,7 @@ class GoogleAnalyticsBasicTest extends DrupalWebTestCase { $this->assertRaw($ua_code, '[testGoogleAnalyticsPageVisibility]: Tracking code is displayed on admin page.'); $this->drupalGet('admin/config/system/googleanalytics'); // Checking for tracking code URI here, as $ua_code is displayed in the form. - $this->assertRaw('google-analytics.com/ga.js', '[testGoogleAnalyticsPageVisibility]: Tracking code is displayed on admin subpage.'); + $this->assertRaw('https://www.google-analytics.com/analytics.js', '[testGoogleAnalyticsPageVisibility]: Tracking code is displayed on admin subpage.'); $this->drupalGet(''); $this->assertNoRaw($ua_code, '[testGoogleAnalyticsPageVisibility]: Tracking code is NOT displayed on front page.'); @@ -80,31 +126,33 @@ class GoogleAnalyticsBasicTest extends DrupalWebTestCase { // Enable tracking code for all user roles. variable_set('googleanalytics_roles', array()); + $base_path = base_path(); + // Test whether 403 forbidden tracking code is shown if user has no access. $this->drupalGet('admin'); - $this->assertRaw('/403.html', '[testGoogleAnalyticsPageVisibility]: 403 Forbidden tracking code shown if user has no access.'); + $this->assertRaw($base_path . '403.html', '[testGoogleAnalyticsPageVisibility]: 403 Forbidden tracking code shown if user has no access.'); // Test whether 404 not found tracking code is shown on non-existent pages. $this->drupalGet($this->randomName(64)); - $this->assertRaw('/404.html', '[testGoogleAnalyticsPageVisibility]: 404 Not Found tracking code shown on non-existent page.'); + $this->assertRaw($base_path . '404.html', '[testGoogleAnalyticsPageVisibility]: 404 Not Found tracking code shown on non-existent page.'); // DNT Tests: - // Enable caching of pages for anonymous users. + // Enable system internal page cache for anonymous users. variable_set('cache', 1); // Test whether DNT headers will fail to disable embedding of tracking code. $this->drupalGet('', array(), array('DNT: 1')); - $this->assertRaw('_gaq.push(["_trackPageview"]);', '[testGoogleAnalyticsDNTVisibility]: DNT header send from client, but page caching is enabled and tracker cannot removed.'); - // DNT works only with caching of pages for anonymous users disabled. + $this->assertRaw('ga("send", "pageview");', '[testGoogleAnalyticsDNTVisibility]: DNT header send from client, but page caching is enabled and tracker cannot removed.'); + // DNT works only with system internal page cache for anonymous users disabled. variable_set('cache', 0); $this->drupalGet(''); - $this->assertRaw('_gaq.push(["_trackPageview"]);', '[testGoogleAnalyticsDNTVisibility]: Tracking is enabled without DNT header.'); + $this->assertRaw('ga("send", "pageview");', '[testGoogleAnalyticsDNTVisibility]: Tracking is enabled without DNT header.'); // Test whether DNT header is able to remove the tracking code. $this->drupalGet('', array(), array('DNT: 1')); - $this->assertNoRaw('_gaq.push(["_trackPageview"]);', '[testGoogleAnalyticsDNTVisibility]: DNT header received from client. Tracking has been disabled by browser.'); + $this->assertNoRaw('ga("send", "pageview");', '[testGoogleAnalyticsDNTVisibility]: DNT header received from client. Tracking has been disabled by browser.'); // Disable DNT feature and see if tracker is still embedded. variable_set('googleanalytics_privacy_donottrack', 0); $this->drupalGet('', array(), array('DNT: 1')); - $this->assertRaw('_gaq.push(["_trackPageview"]);', '[testGoogleAnalyticsDNTVisibility]: DNT feature is disabled, DNT header from browser has been ignored.'); + $this->assertRaw('ga("send", "pageview");', '[testGoogleAnalyticsDNTVisibility]: DNT feature is disabled, DNT header from browser has been ignored.'); } function testGoogleAnalyticsTrackingCode() { @@ -118,40 +166,75 @@ class GoogleAnalyticsBasicTest extends DrupalWebTestCase { /* Sample JS code as added to page: - + */ // Test whether tracking code uses latest JS. variable_set('googleanalytics_cache', 0); $this->drupalGet(''); - $this->assertRaw('google-analytics.com/ga.js', '[testGoogleAnalyticsTrackingCode]: Latest tracking code used.'); - - // Test whether the alternate doubleclick library is used - variable_set('googleanalytics_trackdoubleclick', 1); - $this->drupalGet(''); - $this->assertRaw('stats.g.doubleclick.net/dc.js', '[testGoogleAnalyticsTrackingCode]: Doubleclick tracking code used.'); + $this->assertRaw('https://www.google-analytics.com/analytics.js', '[testGoogleAnalyticsTrackingCode]: Latest tracking code used.'); // Test whether anonymize visitors IP address feature has been enabled. + variable_set('googleanalytics_tracker_anonymizeip', 0); $this->drupalGet(''); - $this->assertNoRaw('_gaq.push(["_gat._anonymizeIp"]);', '[testGoogleAnalyticsTrackingCode]: Anonymize visitors IP address not found on frontpage.'); + $this->assertNoRaw('ga("set", "anonymizeIp", true);', '[testGoogleAnalyticsTrackingCode]: Anonymize visitors IP address not found on frontpage.'); // Enable anonymizing of IP addresses. variable_set('googleanalytics_tracker_anonymizeip', 1); $this->drupalGet(''); - $this->assertRaw('_gaq.push(["_gat._anonymizeIp"]);', '[testGoogleAnalyticsTrackingCode]: Anonymize visitors IP address found on frontpage.'); + $this->assertRaw('ga("set", "anonymizeIp", true);', '[testGoogleAnalyticsTrackingCode]: Anonymize visitors IP address found on frontpage.'); + + // Test if track Enhanced Link Attribution is enabled. + variable_set('googleanalytics_tracklinkid', 1); + $this->drupalGet(''); + $this->assertRaw('ga("require", "linkid", "linkid.js");', '[testGoogleAnalyticsTrackingCode]: Tracking code for Enhanced Link Attribution is enabled.'); + + // Test if track Enhanced Link Attribution is disabled. + variable_set('googleanalytics_tracklinkid', 0); + $this->drupalGet(''); + $this->assertNoRaw('ga("require", "linkid", "linkid.js");', '[testGoogleAnalyticsTrackingCode]: Tracking code for Enhanced Link Attribution is not enabled.'); + + // Test if tracking of User ID is enabled. + variable_set('googleanalytics_trackuserid', 1); + $this->drupalGet(''); + $this->assertRaw(', {"cookieDomain":"auto","userId":"', '[testGoogleAnalyticsTrackingCode]: Tracking code for User ID is enabled.'); + + // Test if tracking of User ID is disabled. + variable_set('googleanalytics_trackuserid', 0); + $this->drupalGet(''); + $this->assertNoRaw(', {"cookieDomain":"auto","userId":"', '[testGoogleAnalyticsTrackingCode]: Tracking code for User ID is disabled.'); + + // Test if tracking of url fragments is enabled. + variable_set('googleanalytics_trackurlfragments', 1); + $this->drupalGet(''); + $this->assertRaw('ga("set", "page", location.pathname + location.search + location.hash);', '[testGoogleAnalyticsTrackingCode]: Tracking code for url fragments is enabled.'); + + // Test if tracking of url fragments is disabled. + variable_set('googleanalytics_trackurlfragments', 0); + $this->drupalGet(''); + $this->assertNoRaw('ga("set", "page", location.pathname + location.search + location.hash);', '[testGoogleAnalyticsTrackingCode]: Tracking code for url fragments is not enabled.'); + + // Test if track display features is enabled. + variable_set('googleanalytics_trackdoubleclick', 1); + $this->drupalGet(''); + $this->assertRaw('ga("require", "displayfeatures");', '[testGoogleAnalyticsTrackingCode]: Tracking code for display features is enabled.'); + + // Test if track display features is disabled. + variable_set('googleanalytics_trackdoubleclick', 0); + $this->drupalGet(''); + $this->assertNoRaw('ga("require", "displayfeatures");', '[testGoogleAnalyticsTrackingCode]: Tracking code for display features is not enabled.'); // Test whether single domain tracking is active. $this->drupalGet(''); - $this->assertNoRaw('_gaq.push(["_setDomainName"', '[testGoogleAnalyticsTrackingCode]: Single domain tracking is active.'); + $this->assertRaw('{"cookieDomain":"auto"}', '[testGoogleAnalyticsTrackingCode]: Single domain tracking is active.'); // Enable "One domain with multiple subdomains". variable_set('googleanalytics_domain_mode', 1); @@ -161,36 +244,63 @@ class GoogleAnalyticsBasicTest extends DrupalWebTestCase { // TODO: Workaround to run tests successfully. This feature cannot tested reliable. global $cookie_domain; if (count(explode('.', $cookie_domain)) > 2 && !is_numeric(str_replace('.', '', $cookie_domain))) { - $this->assertRaw('_gaq.push(["_setDomainName",', '[testGoogleAnalyticsTrackingCode]: One domain with multiple subdomains is active on real host.'); + $this->assertRaw('{"cookieDomain":"' . $cookie_domain . '"}', '[testGoogleAnalyticsTrackingCode]: One domain with multiple subdomains is active on real host.'); } else { // Special cases, Localhost and IP addresses don't show '_setDomainName'. - $this->assertNoRaw('_gaq.push(["_setDomainName",', '[testGoogleAnalyticsTrackingCode]: One domain with multiple subdomains may be active on localhost (test result is not reliable).'); + $this->assertNoRaw('{"cookieDomain":"' . $cookie_domain . '"}', '[testGoogleAnalyticsTrackingCode]: One domain with multiple subdomains may be active on localhost (test result is not reliable).'); } // Enable "Multiple top-level domains" tracking. variable_set('googleanalytics_domain_mode', 2); variable_set('googleanalytics_cross_domains', "www.example.com\nwww.example.net"); $this->drupalGet(''); - $this->assertRaw('_gaq.push(["_setDomainName", "none"]);', '[testGoogleAnalyticsTrackingCode]: _setDomainName: "none" found. Cross domain tracking is active.'); - $this->assertRaw('_gaq.push(["_setAllowLinker", true]);', '[testGoogleAnalyticsTrackingCode]: _setAllowLinker: true found. Cross domain tracking is active.'); + $this->assertRaw('ga("create", "' . $ua_code . '", {"cookieDomain":"auto","allowLinker":true', '[testGoogleAnalyticsTrackingCode]: "allowLinker" has been found. Cross domain tracking is active.'); + $this->assertRaw('ga("require", "linker");', '[testGoogleAnalyticsTrackingCode]: Require linker has been found. Cross domain tracking is active.'); + $this->assertRaw('ga("linker:autoLink", ["www.example.com","www.example.net"]);', '[testGoogleAnalyticsTrackingCode]: "linker:autoLink" has been found. Cross domain tracking is active.'); + $this->assertRaw('"trackDomainMode":2,', '[testGoogleAnalyticsTrackingCode]: Domain mode value is of type integer.'); $this->assertRaw('"trackCrossDomains":["www.example.com","www.example.net"]', '[testGoogleAnalyticsTrackingCode]: Cross domain tracking with www.example.com and www.example.net is active.'); + variable_set('googleanalytics_domain_mode', 0); - // Test whether the BEFORE and AFTER code is added to the tracker. - variable_set('googleanalytics_codesnippet_before', '_setDetectFlash(false);'); - variable_set('googleanalytics_codesnippet_after', '_gaq.push(["t2._setAccount", "UA-123456-3"]);_gaq.push(["t2._trackPageview"]);'); + // Test whether debugging script has been enabled. + variable_set('googleanalytics_debug', 1); $this->drupalGet(''); - $this->assertRaw('_setDetectFlash(false);', '[testGoogleAnalyticsTrackingCode]: Before codesnippet has been found with "Flash" detection disabled.'); - $this->assertRaw('t2._setAccount', '[testGoogleAnalyticsTrackingCode]: After codesnippet with "t2" tracker has been found.'); + $this->assertRaw('https://www.google-analytics.com/analytics_debug.js', '[testGoogleAnalyticsTrackingCode]: Google debugging script has been enabled.'); + + // Check if text and link is shown on 'Status Reports' page. + // Requires 'administer site configuration' permission. + $this->drupalGet('admin/reports/status'); + $this->assertRaw(t('Google Analytics module has debugging enabled. Please disable debugging setting in production sites from the Google Analytics settings page.', array('@url' => url('admin/config/system/googleanalytics'))), '[testGoogleAnalyticsConfiguration]: Debugging enabled is shown on Status Reports page.'); + + // Test whether debugging script has been disabled. + variable_set('googleanalytics_debug', 0); + $this->drupalGet(''); + $this->assertRaw('https://www.google-analytics.com/analytics.js', '[testGoogleAnalyticsTrackingCode]: Google debugging script has been disabled.'); + + // Test whether the CREATE and BEFORE and AFTER code is added to the tracker. + $codesnippet_create = array( + 'cookieDomain' => 'foo.example.com', + 'cookieName' => 'myNewName', + 'cookieExpires' => 20000, + 'allowAnchor' => TRUE, + 'sampleRate' => 4.3, + ); + variable_set('googleanalytics_codesnippet_create', $codesnippet_create); + variable_set('googleanalytics_codesnippet_before', 'ga("set", "forceSSL", true);'); + variable_set('googleanalytics_codesnippet_after', 'ga("create", "UA-123456-3", {"name": "newTracker"});ga("newTracker.send", "pageview");'); + $this->drupalGet(''); + $this->assertRaw('ga("create", "' . $ua_code . '", {"cookieDomain":"foo.example.com","cookieName":"myNewName","cookieExpires":20000,"allowAnchor":true,"sampleRate":4.3});', '[testGoogleAnalyticsTrackingCode]: Create only fields have been found.'); + $this->assertRaw('ga("set", "forceSSL", true);', '[testGoogleAnalyticsTrackingCode]: Before codesnippet will force http pages to also send all beacons using https.'); + $this->assertRaw('ga("create", "UA-123456-3", {"name": "newTracker"});', '[testGoogleAnalyticsTrackingCode]: After codesnippet with "newTracker" tracker has been found.'); } } -class GoogleAnalyticsCustomVariablesTest extends DrupalWebTestCase { +class GoogleAnalyticsCustomDimensionsAndMetricsTest extends DrupalWebTestCase { public static function getInfo() { return array( - 'name' => t('Google Analytics Custom Variables tests'), - 'description' => t('Test custom variables functionality of Google Analytics module.'), + 'name' => 'Google Analytics custom dimensions and metrics tests', + 'description' => 'Test custom dimensions and metrics functionality of Google Analytics module.', 'group' => 'Google Analytics', 'dependencies' => array('token'), ); @@ -206,110 +316,217 @@ class GoogleAnalyticsCustomVariablesTest extends DrupalWebTestCase { // User to set up google_analytics. $this->admin_user = $this->drupalCreateUser($permissions); + $this->drupalLogin($this->admin_user); } - function testGoogleAnalyticsCustomVariables() { + function testGoogleAnalyticsCustomDimensions() { $ua_code = 'UA-123456-3'; variable_set('googleanalytics_account', $ua_code); // Basic test if the feature works. - $custom_vars = array( - 'slots' => array( - 1 => array( - 'slot' => 1, - 'name' => 'Foo 1', - 'value' => 'Bar 1', - 'scope' => 3, - ), - 2 => array( - 'slot' => 2, - 'name' => 'Foo 2', - 'value' => 'Bar 2', - 'scope' => 2, - ), - 3 => array( - 'slot' => 3, - 'name' => 'Foo 3', - 'value' => 'Bar 3', - 'scope' => 3, - ), - 4 => array( - 'slot' => 4, - 'name' => 'Foo 4', - 'value' => 'Bar 4', - 'scope' => 2, - ), - 5 => array( - 'slot' => 5, - 'name' => 'Foo 5', - 'value' => 'Bar 5', - 'scope' => 1, - ), - ) + $googleanalytics_custom_dimension = array( + 1 => array( + 'index' => 1, + 'value' => 'Bar 1', + ), + 2 => array( + 'index' => 2, + 'value' => 'Bar 2', + ), + 3 => array( + 'index' => 3, + 'value' => 'Bar 3', + ), + 4 => array( + 'index' => 4, + 'value' => 'Bar 4', + ), + 5 => array( + 'index' => 5, + 'value' => 'Bar 5', + ), ); - variable_set('googleanalytics_custom_var', $custom_vars); + variable_set('googleanalytics_custom_dimension', $googleanalytics_custom_dimension); $this->drupalGet(''); - foreach ($custom_vars['slots'] as $slot) { - $this->assertRaw("_gaq.push(['_setCustomVar', " . $slot['slot'] . ", \"" . $slot['name'] . "\", \"" . $slot['value'] . "\", " . $slot['scope'] . "]);", '[testGoogleAnalyticsCustomVariables]: _setCustomVar ' . $slot['slot'] . ' is shown.'); + foreach ($googleanalytics_custom_dimension as $dimension) { + $this->assertRaw('ga("set", ' . drupal_json_encode('dimension' . $dimension['index']) . ', ' . drupal_json_encode($dimension['value']) . ');', '[testGoogleAnalyticsCustomDimensionsAndMetrics]: Dimension #' . $dimension['index'] . ' is shown.'); } - // Test whether tokens are replaced in custom variable names. + // Test whether tokens are replaced in custom dimension values. $site_slogan = $this->randomName(16); variable_set('site_slogan', $site_slogan); - $custom_vars = array( - 'slots' => array( - 1 => array( - 'slot' => 1, - 'name' => 'Name: [site:slogan]', - 'value' => 'Value: [site:slogan]', - 'scope' => 3, - ), - 2 => array( - 'slot' => 2, - 'name' => '', - 'value' => $this->randomName(16), - 'scope' => 1, - ), - 3 => array( - 'slot' => 3, - 'name' => $this->randomName(16), - 'value' => '', - 'scope' => 2, - ), - 4 => array( - 'slot' => 4, - 'name' => '', - 'value' => '', - 'scope' => 3, - ), - 5 => array( - 'slot' => 5, - 'name' => '', - 'value' => '', - 'scope' => 3, - ), - ) + $googleanalytics_custom_dimension = array( + 1 => array( + 'index' => 1, + 'value' => 'Value: [site:slogan]', + ), + 2 => array( + 'index' => 2, + 'value' => $this->randomName(16), + ), + 3 => array( + 'index' => 3, + 'value' => '', + ), + // #2300701: Custom dimensions and custom metrics not outputed on zero value. + 4 => array( + 'index' => 4, + 'value' => '0', + ), ); - variable_set('googleanalytics_custom_var', $custom_vars); - $this->verbose('
' . print_r($custom_vars, TRUE) . '
'); + variable_set('googleanalytics_custom_dimension', $googleanalytics_custom_dimension); + $this->verbose('
' . print_r($googleanalytics_custom_dimension, TRUE) . '
'); $this->drupalGet(''); - $this->assertRaw("_gaq.push(['_setCustomVar', 1, \"Name: $site_slogan\", \"Value: $site_slogan\", 3]", '[testGoogleAnalyticsCustomVariables]: Tokens have been replaced in custom variable.'); - $this->assertNoRaw("_gaq.push(['_setCustomVar', 2,", '[testGoogleAnalyticsCustomVariables]: Value with empty name is not shown.'); - $this->assertNoRaw("_gaq.push(['_setCustomVar', 3,", '[testGoogleAnalyticsCustomVariables]: Name with empty value is not shown.'); - $this->assertNoRaw("_gaq.push(['_setCustomVar', 4,", '[testGoogleAnalyticsCustomVariables]: Empty name and value is not shown.'); - $this->assertNoRaw("_gaq.push(['_setCustomVar', 5,", '[testGoogleAnalyticsCustomVariables]: Empty name and value is not shown.'); + $this->assertRaw('ga("set", ' . drupal_json_encode('dimension1') . ', ' . drupal_json_encode("Value: $site_slogan") . ');', '[testGoogleAnalyticsCustomDimensionsAndMetrics]: Tokens have been replaced in dimension value.'); + $this->assertRaw('ga("set", ' . drupal_json_encode('dimension2') . ', ' . drupal_json_encode($googleanalytics_custom_dimension['2']['value']) . ');', '[testGoogleAnalyticsCustomDimensionsAndMetrics]: Random value is shown.'); + $this->assertNoRaw('ga("set", ' . drupal_json_encode('dimension3') . ', ' . drupal_json_encode('') . ');', '[testGoogleAnalyticsCustomDimensionsAndMetrics]: Empty value is not shown.'); + $this->assertRaw('ga("set", ' . drupal_json_encode('dimension4') . ', ' . drupal_json_encode('0') . ');', '[testGoogleAnalyticsCustomDimensionsAndMetrics]: Value 0 is shown.'); } + + function testGoogleAnalyticsCustomMetrics() { + $ua_code = 'UA-123456-3'; + variable_set('googleanalytics_account', $ua_code); + + // Basic test if the feature works. + $googleanalytics_custom_metric = array( + 1 => array( + 'index' => 1, + 'value' => '6', + ), + 2 => array( + 'index' => 2, + 'value' => '8000', + ), + 3 => array( + 'index' => 3, + 'value' => '7.8654', + ), + 4 => array( + 'index' => 4, + 'value' => '1123.4', + ), + 5 => array( + 'index' => 5, + 'value' => '5,67', + ), + ); + + variable_set('googleanalytics_custom_metric', $googleanalytics_custom_metric); + $this->drupalGet(''); + + foreach ($googleanalytics_custom_metric as $metric) { + $this->assertRaw('ga("set", ' . drupal_json_encode('metric' . $metric['index']) . ', ' . drupal_json_encode((float) $metric['value']) . ');', '[testGoogleAnalyticsCustomDimensionsAndMetrics]: Metric #' . $metric['index'] . ' is shown.'); + } + + // Test whether tokens are replaced in custom metric values. + $googleanalytics_custom_metric = array( + 1 => array( + 'index' => 1, + 'value' => '[current-user:roles:count]', + ), + 2 => array( + 'index' => 2, + 'value' => mt_rand(), + ), + 3 => array( + 'index' => 3, + 'value' => '', + ), + // #2300701: Custom dimensions and custom metrics not outputed on zero value. + 4 => array( + 'index' => 4, + 'value' => '0', + ), + ); + variable_set('googleanalytics_custom_metric', $googleanalytics_custom_metric); + $this->verbose('
' . print_r($googleanalytics_custom_metric, TRUE) . '
'); + + $this->drupalGet(''); + $this->assertRaw('ga("set", ' . drupal_json_encode('metric1') . ', ', '[testGoogleAnalyticsCustomDimensionsAndMetrics]: Tokens have been replaced in metric value.'); + $this->assertRaw('ga("set", ' . drupal_json_encode('metric2') . ', ' . drupal_json_encode($googleanalytics_custom_metric['2']['value']) . ');', '[testGoogleAnalyticsCustomDimensionsAndMetrics]: Random value is shown.'); + $this->assertNoRaw('ga("set", ' . drupal_json_encode('metric3') . ', ' . drupal_json_encode('') . ');', '[testGoogleAnalyticsCustomDimensionsAndMetrics]: Empty value is not shown.'); + $this->assertRaw('ga("set", ' . drupal_json_encode('metric4') . ', ' . drupal_json_encode(0) . ');', '[testGoogleAnalyticsCustomDimensionsAndMetrics]: Value 0 is shown.'); + } + + /** + * Tests if Custom Dimensions token form validation works. + */ + public function testGoogleAnalyticsCustomDimensionsTokenFormValidation() { + $ua_code = 'UA-123456-1'; + + // Check form validation. + $edit['googleanalytics_account'] = $ua_code; + $edit['googleanalytics_custom_dimension[indexes][1][value]'] = '[current-user:name]'; + $edit['googleanalytics_custom_dimension[indexes][2][value]'] = '[current-user:edit-url]'; + $edit['googleanalytics_custom_dimension[indexes][3][value]'] = '[user:name]'; + $edit['googleanalytics_custom_dimension[indexes][4][value]'] = '[term:name]'; + $edit['googleanalytics_custom_dimension[indexes][5][value]'] = '[term:tid]'; + + $this->drupalPost('admin/config/system/googleanalytics', $edit, t('Save configuration')); + + $this->assertRaw(t('The %element-title is using the following forbidden tokens with personal identifying information: @invalid-tokens.', array('%element-title' => t('Custom dimension value #@index', array('@index' => 1)), '@invalid-tokens' => implode(', ', array('[current-user:name]'))))); + $this->assertRaw(t('The %element-title is using the following forbidden tokens with personal identifying information: @invalid-tokens.', array('%element-title' => t('Custom dimension value #@index', array('@index' => 2)), '@invalid-tokens' => implode(', ', array('[current-user:edit-url]'))))); + $this->assertRaw(t('The %element-title is using the following forbidden tokens with personal identifying information: @invalid-tokens.', array('%element-title' => t('Custom dimension value #@index', array('@index' => 3)), '@invalid-tokens' => implode(', ', array('[user:name]'))))); + // BUG #2037595 + //$this->assertNoRaw(t('The %element-title is using the following forbidden tokens with personal identifying information: @invalid-tokens.', array('%element-title' => t('Custom dimension value #@index', array('@index' => 4)), '@invalid-tokens' => implode(', ', array('[term:name]'))))); + //$this->assertNoRaw(t('The %element-title is using the following forbidden tokens with personal identifying information: @invalid-tokens.', array('%element-title' => t('Custom dimension value #@index', array('@index' => 5)), '@invalid-tokens' => implode(', ', array('[term:tid]'))))); + } +} + +/** + * Test custom url functionality of Google Analytics module. + */ +class GoogleAnalyticsCustomUrls extends DrupalWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Google Analytics custom url tests', + 'description' => 'Test custom url functionality of Google Analytics module.', + 'group' => 'Google Analytics', + ); + } + + function setUp() { + parent::setUp('googleanalytics'); + + $permissions = array( + 'access administration pages', + 'administer google analytics', + ); + + // User to set up google_analytics. + $this->admin_user = $this->drupalCreateUser($permissions); + } + + /** + * Tests if user password page urls are overridden. + */ + public function testGoogleAnalyticsUserPasswordPage() { + $base_path = base_path(); + $ua_code = 'UA-123456-4'; + variable_set('googleanalytics_account', $ua_code); + + $this->drupalGet('user/password', array('query' => array('name' => 'foo'))); + $this->assertRaw('ga("set", "page", "' . $base_path . 'user/password"'); + + $this->drupalGet('user/password', array('query' => array('name' => 'foo@example.com'))); + $this->assertRaw('ga("set", "page", "' . $base_path . 'user/password"'); + + $this->drupalGet('user/password'); + $this->assertNoRaw('ga("set", "page",', '[testGoogleAnalyticsCustomUrls]: Custom url not set.'); + } + } class GoogleAnalyticsStatusMessagesTest extends DrupalWebTestCase { public static function getInfo() { return array( - 'name' => t('Google Analytics status messages tests'), - 'description' => t('Test status messages functionality of Google Analytics module.'), + 'name' => 'Google Analytics status messages tests', + 'description' => 'Test status messages functionality of Google Analytics module.', 'group' => 'Google Analytics', ); } @@ -333,20 +550,20 @@ class GoogleAnalyticsStatusMessagesTest extends DrupalWebTestCase { // Enable logging of errors only. variable_set('googleanalytics_trackmessages', array('error' => 'error')); - $this->drupalPost('user/login', array(), 'Log in'); - $this->assertRaw('_gaq.push(["_trackEvent", "Messages", "Error message", "Username field is required."]);', '[testGoogleAnalyticsStatusMessages]: _trackEvent "Username field is required." is shown.'); - $this->assertRaw('_gaq.push(["_trackEvent", "Messages", "Error message", "Password field is required."]);', '[testGoogleAnalyticsStatusMessages]: _trackEvent "Password field is required." is shown.'); + $this->drupalPost('user/login', array(), t('Log in')); + $this->assertRaw('ga("send", "event", "Messages", "Error message", "Username field is required.");', '[testGoogleAnalyticsStatusMessages]: Event message "Username field is required." is shown.'); + $this->assertRaw('ga("send", "event", "Messages", "Error message", "Password field is required.");', '[testGoogleAnalyticsStatusMessages]: Event message "Password field is required." is shown.'); // @todo: investigate why drupal_set_message() fails. //drupal_set_message('Example status message.', 'status'); //drupal_set_message('Example warning message.', 'warning'); //drupal_set_message('Example error message.', 'error'); - //drupal_set_message('Example error message with html tags and link.', 'error'); + //drupal_set_message('Example error message with html tags and link.', 'error'); //$this->drupalGet(''); - //$this->assertNoRaw('_gaq.push(["_trackEvent", "Messages", "Status message", "Example status message."]);', '[testGoogleAnalyticsStatusMessages]: Example status message is not enabled for tracking.'); - //$this->assertNoRaw('_gaq.push(["_trackEvent", "Messages", "Warning message", "Example warning message."]);', '[testGoogleAnalyticsStatusMessages]: Example warning message is not enabled for tracking.'); - //$this->assertRaw('_gaq.push(["_trackEvent", "Messages", "Error message", "Example error message."]);', '[testGoogleAnalyticsStatusMessages]: Example error message is shown.'); - //$this->assertRaw('_gaq.push(["_trackEvent", "Messages", "Error message", "Example error message with html tags and link."]);', '[testGoogleAnalyticsStatusMessages]: HTML has been stripped successful from Example error message with html tags and link.'); + //$this->assertNoRaw('ga("send", "event", "Messages", "Status message", "Example status message.");', '[testGoogleAnalyticsStatusMessages]: Example status message is not enabled for tracking.'); + //$this->assertNoRaw('ga("send", "event", "Messages", "Warning message", "Example warning message.");', '[testGoogleAnalyticsStatusMessages]: Example warning message is not enabled for tracking.'); + //$this->assertRaw('ga("send", "event", "Messages", "Error message", "Example error message.");', '[testGoogleAnalyticsStatusMessages]: Example error message is shown.'); + //$this->assertRaw('ga("send", "event", "Messages", "Error message", "Example error message with html tags and link.");', '[testGoogleAnalyticsStatusMessages]: HTML has been stripped successful from Example error message with html tags and link.'); } } @@ -354,8 +571,8 @@ class GoogleAnalyticsRolesTest extends DrupalWebTestCase { public static function getInfo() { return array( - 'name' => t('Google Analytics role tests'), - 'description' => t('Test roles functionality of Google Analytics module.'), + 'name' => 'Google Analytics role tests', + 'description' => 'Test roles functionality of Google Analytics module.', 'group' => 'Google Analytics', ); } @@ -438,5 +655,165 @@ class GoogleAnalyticsRolesTest extends DrupalWebTestCase { $this->drupalGet(''); $this->assertRaw($ua_code, '[testGoogleAnalyticsRoleVisibility]: Tracking code is displayed on frontpage for included anonymous users.'); } +} + +class GoogleAnalyticsSearchTest extends DrupalWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Google Analytics search tests', + 'description' => 'Test search functionality of Google Analytics module.', + 'group' => 'Google Analytics', + ); + } + + function setUp() { + parent::setUp('googleanalytics', 'search', 'node'); + + $permissions = array( + 'access administration pages', + 'administer google analytics', + 'search content', + 'create page content', + 'edit own page content', + ); + + // User to set up google_analytics. + $this->admin_user = $this->drupalCreateUser($permissions); + $this->drupalLogin($this->admin_user); + } + + function testGoogleAnalyticsSearchTracking() { + $ua_code = 'UA-123456-1'; + variable_set('googleanalytics_account', $ua_code); + + // Check tracking code visibility. + $this->drupalGet(''); + $this->assertRaw($ua_code, '[testGoogleAnalyticsSearch]: Tracking code is displayed for authenticated users.'); + + $this->drupalGet('search/node'); + $this->assertNoRaw('ga("set", "page",', '[testGoogleAnalyticsSearch]: Custom url not set.'); + + // Enable site search support. + variable_set('googleanalytics_site_search', 1); + + // Search for random string. + $search = array(); + $search['keys'] = $this->randomName(8); + + // Create a node to search for. + $langcode = LANGUAGE_NONE; + $edit = array(); + $edit['title'] = 'This is a test title'; + $edit["body[$langcode][0][value]"] = 'This test content contains ' . $search['keys'] . ' string.'; + + // Fire a search, it's expected to get 0 results. + $this->drupalPost('search/node', $search, t('Search')); + $this->assertRaw('ga("set", "page", (window.googleanalytics_search_results) ?', '[testGoogleAnalyticsSearch]: Search results tracker is displayed.'); + $this->assertRaw('window.googleanalytics_search_results = 0;', '[testGoogleAnalyticsSearch]: Search yielded no results.'); + + // Save the node. + $this->drupalPost('node/add/page', $edit, t('Save')); + $this->assertText(t('@type @title has been created.', array('@type' => 'Basic page', '@title' => $edit['title'])), 'Node was created.'); + + // Index the node or it cannot found. + $this->cronRun(); + + $this->drupalPost('search/node', $search, t('Search')); + $this->assertRaw('ga("set", "page", (window.googleanalytics_search_results) ?', '[testGoogleAnalyticsSearch]: Search results tracker is displayed.'); + $this->assertRaw('window.googleanalytics_search_results = 1;', '[testGoogleAnalyticsSearch]: One search result found.'); + + $this->drupalPost('node/add/page', $edit, t('Save')); + $this->assertText(t('@type @title has been created.', array('@type' => 'Basic page', '@title' => $edit['title'])), 'Node was created.'); + + // Index the node or it cannot found. + $this->cronRun(); + + $this->drupalPost('search/node', $search, t('Search')); + $this->assertRaw('ga("set", "page", (window.googleanalytics_search_results) ?', '[testGoogleAnalyticsSearch]: Search results tracker is displayed.'); + $this->assertRaw('window.googleanalytics_search_results = 2;', '[testGoogleAnalyticsSearch]: Two search results found.'); + } + +} + +class GoogleAnalyticsPhpFilterTest extends DrupalWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Google Analytics php filter tests', + 'description' => 'Test php filter functionality of Google Analytics module.', + 'group' => 'Google Analytics', + ); + } + + function setUp() { + parent::setUp('googleanalytics', 'php'); + + // Administrator with all permissions. + $permissions_admin_user = array( + 'access administration pages', + 'administer google analytics', + 'use PHP for tracking visibility', + ); + $this->admin_user = $this->drupalCreateUser($permissions_admin_user); + + // Administrator who cannot configure tracking visibility with PHP. + $permissions_delegated_admin_user = array( + 'access administration pages', + 'administer google analytics', + ); + $this->delegated_admin_user = $this->drupalCreateUser($permissions_delegated_admin_user); + } + + function testGoogleAnalyticsPhpFilter() { + $ua_code = 'UA-123456-1'; + $this->drupalLogin($this->admin_user); + + $edit = array(); + $edit['googleanalytics_account'] = $ua_code; + $edit['googleanalytics_visibility_pages'] = 2; + $edit['googleanalytics_pages'] = ''; + $this->drupalPost('admin/config/system/googleanalytics', $edit, t('Save configuration')); + + // Compare saved setting with posted setting. + $googleanalytics_pages = variable_get('googleanalytics_pages', $this->randomName(8)); + $this->assertEqual('', $googleanalytics_pages, '[testGoogleAnalyticsPhpFilter]: PHP code snippet is intact.'); + + // Check tracking code visibility. + variable_set('googleanalytics_pages', ''); + $this->drupalGet(''); + $this->assertRaw('https://www.google-analytics.com/analytics.js', '[testGoogleAnalyticsPhpFilter]: Tracking is displayed on frontpage page.'); + $this->drupalGet('admin'); + $this->assertRaw('https://www.google-analytics.com/analytics.js', '[testGoogleAnalyticsPhpFilter]: Tracking is displayed on admin page.'); + + variable_set('googleanalytics_pages', ''); + $this->drupalGet(''); + $this->assertNoRaw('https://www.google-analytics.com/analytics.js', '[testGoogleAnalyticsPhpFilter]: Tracking is not displayed on frontpage page.'); + + // Test administration form. + variable_set('googleanalytics_pages', ''); + $this->drupalGet('admin/config/system/googleanalytics'); + $this->assertRaw(t('Pages on which this PHP code returns TRUE (experts only)'), '[testGoogleAnalyticsPhpFilter]: Permission to administer PHP for tracking visibility.'); + $this->assertRaw(check_plain(''), '[testGoogleAnalyticsPhpFilter]: PHP code snippted is displayed.'); + + // Login the delegated user and check if fields are visible. + $this->drupalLogin($this->delegated_admin_user); + $this->drupalGet('admin/config/system/googleanalytics'); + $this->assertNoRaw(t('Pages on which this PHP code returns TRUE (experts only)'), '[testGoogleAnalyticsPhpFilter]: No permission to administer PHP for tracking visibility.'); + $this->assertNoRaw(check_plain(''), '[testGoogleAnalyticsPhpFilter]: No permission to view PHP code snippted.'); + + // Set a different value and verify that this is still the same after the post. + variable_set('googleanalytics_pages', ''); + + $edit = array(); + $edit['googleanalytics_account'] = $ua_code; + $this->drupalPost('admin/config/system/googleanalytics', $edit, t('Save configuration')); + + // Compare saved setting with posted setting. + $googleanalytics_visibility_pages = variable_get('googleanalytics_visibility_pages', 0); + $googleanalytics_pages = variable_get('googleanalytics_pages', $this->randomName(8)); + $this->assertEqual(2, $googleanalytics_visibility_pages, '[testGoogleAnalyticsPhpFilter]: Pages on which this PHP code returns TRUE is selected.'); + $this->assertEqual('', $googleanalytics_pages, '[testGoogleAnalyticsPhpFilter]: PHP code snippet is intact.'); + } } diff --git a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.test.js b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.test.js new file mode 100644 index 0000000..f22d6f1 --- /dev/null +++ b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.test.js @@ -0,0 +1,121 @@ +(function ($) { + +/** + * This file is for developers only. + * + * This tests are made for the javascript functions used in GA module. + * These tests verify if the return values are properly working. + * + * Hopefully this can be added somewhere else once Drupal core has JavaScript + * unit testing integrated. + */ + +"use strict"; + +Drupal.googleanalytics.test = {}; + +Drupal.googleanalytics.test.assertSame = function (value1, value2, message) { + if (value1 === value2) { + console.info(message); + } + else { + console.error(message); + } +}; + +Drupal.googleanalytics.test.assertNotSame = function (value1, value2, message) { + if (value1 !== value2) { + console.info(message); + } + else { + console.error(message); + } +}; + +Drupal.googleanalytics.test.assertTrue = function (value1, message) { + if (value1 === true) { + console.info(message); + } + else { + console.error(message); + } +}; + +Drupal.googleanalytics.test.assertFalse = function (value1, message) { + if (value1 === false) { + console.info(message); + } + else { + console.error(message); + } +}; + +// Run after the documented is ready or Drupal.settings is undefined. +$(document).ready(function() { + + /** + * Run javascript tests against the GA module. + */ + + // JavaScript debugging + var base_url = window.location.protocol + '//' + window.location.host; + var base_path = window.location.pathname; + console.dir(Drupal); + + console.group("Test 'isDownload':"); + Drupal.googleanalytics.test.assertFalse(Drupal.googleanalytics.isDownload(base_url + Drupal.settings.basePath + 'node/8'), "Verify that '/node/8' url is not detected as file download."); + Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isDownload(base_url + Drupal.settings.basePath + 'files/foo1.zip'), "Verify that '/files/foo1.zip' url is detected as a file download."); + Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isDownload(base_url + Drupal.settings.basePath + 'files/foo1.zip#foo'), "Verify that '/files/foo1.zip#foo' url is detected as a file download."); + Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isDownload(base_url + Drupal.settings.basePath + 'files/foo1.zip?foo=bar'), "Verify that '/files/foo1.zip?foo=bar' url is detected as a file download."); + Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isDownload(base_url + Drupal.settings.basePath + 'files/foo1.zip?foo=bar#foo'), "Verify that '/files/foo1.zip?foo=bar#foo' url is detected as a file download."); + Drupal.googleanalytics.test.assertFalse(Drupal.googleanalytics.isDownload(base_url + Drupal.settings.basePath + 'files/foo2.ddd'), "Verify that '/files/foo2.ddd' url is not detected as file download."); + console.groupEnd(); + + console.group("Test 'isInternal':"); + Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isInternal(base_url + Drupal.settings.basePath + 'node/1'), "Link '" + base_url + Drupal.settings.basePath + "node/2' has been detected as internal link."); + Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isInternal(base_url + Drupal.settings.basePath + 'node/1#foo'), "Link '" + base_url + Drupal.settings.basePath + "node/1#foo' has been detected as internal link."); + Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isInternal(base_url + Drupal.settings.basePath + 'node/1?foo=bar'), "Link '" + base_url + Drupal.settings.basePath + "node/1?foo=bar' has been detected as internal link."); + Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isInternal(base_url + Drupal.settings.basePath + 'node/1?foo=bar#foo'), "Link '" + base_url + Drupal.settings.basePath + "node/1?foo=bar#foo' has been detected as internal link."); + Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isInternal(base_url + Drupal.settings.basePath + 'go/foo'), "Link '" + base_url + Drupal.settings.basePath + "go/foo' has been detected as internal link."); + Drupal.googleanalytics.test.assertFalse(Drupal.googleanalytics.isInternal('https://example.com/node/3'), "Link 'https://example.com/node/3' has been detected as external link."); + console.groupEnd(); + + console.group("Test 'isInternalSpecial':"); + Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isInternalSpecial(base_url + Drupal.settings.basePath + 'go/foo'), "Link '" + base_url + Drupal.settings.basePath + "go/foo' has been detected as special internal link."); + Drupal.googleanalytics.test.assertFalse(Drupal.googleanalytics.isInternalSpecial(base_url + Drupal.settings.basePath + 'node/1'), "Link '" + base_url + Drupal.settings.basePath + "node/1' has been detected as special internal link."); + console.groupEnd(); + + console.group("Test 'getPageUrl':"); + Drupal.google_analytics.test.assertSame(base_path, Drupal.google_analytics.getPageUrl(window.location.href), "Absolute internal URL '" + base_path + "' has been extracted from full qualified url '" + window.location.href + "'."); + Drupal.google_analytics.test.assertSame(base_path, Drupal.google_analytics.getPageUrl(base_path), "Absolute internal URL '" + base_path + "' has been extracted from absolute url '" + base_path + "'."); + //Drupal.googleanalytics.test.assertSame(base_path, Drupal.googleanalytics.getPageUrl(base_url + Drupal.settings.basePath + 'node/1'), "Absolute internal URL '" + Drupal.settings.basePath + "node/1' has been extracted from full qualified url '" + base_url + base_path + "'."); + //Drupal.googleanalytics.test.assertSame(base_path, Drupal.googleanalytics.getPageUrl(Drupal.settings.basePath + 'node/1'), "Absolute internal URL '" + Drupal.settings.basePath + "node/1' has been extracted from absolute url '" + base_path + "'."); + Drupal.googleanalytics.test.assertSame('https://example.com/node/2', Drupal.googleanalytics.getPageUrl('https://example.com/node/2'), "Full qualified external url 'https://example.com/node/2' has been extracted."); + Drupal.googleanalytics.test.assertSame('//example.com/node/2', Drupal.googleanalytics.getPageUrl('//example.com/node/2'), "Full qualified external url '//example.com/node/2' has been extracted."); + console.groupEnd(); + + console.group("Test 'getDownloadExtension':"); + Drupal.googleanalytics.test.assertSame('zip', Drupal.googleanalytics.getDownloadExtension(base_url + Drupal.settings.basePath + '/files/foo1.zip'), "Download extension 'zip' has been found in '" + base_url + Drupal.settings.basePath + "files/foo1.zip'."); + Drupal.googleanalytics.test.assertSame('zip', Drupal.googleanalytics.getDownloadExtension(base_url + Drupal.settings.basePath + '/files/foo1.zip#foo'), "Download extension 'zip' has been found in '" + base_url + Drupal.settings.basePath + "files/foo1.zip#foo'."); + Drupal.googleanalytics.test.assertSame('zip', Drupal.googleanalytics.getDownloadExtension(base_url + Drupal.settings.basePath + '/files/foo1.zip?foo=bar'), "Download extension 'zip' has been found in '" + base_url + Drupal.settings.basePath + "files/foo1.zip?foo=bar'."); + Drupal.googleanalytics.test.assertSame('zip', Drupal.googleanalytics.getDownloadExtension(base_url + Drupal.settings.basePath + '/files/foo1.zip?foo=bar#foo'), "Download extension 'zip' has been found in '" + base_url + Drupal.settings.basePath + "files/foo1.zip?foo=bar'."); + Drupal.googleanalytics.test.assertSame('', Drupal.googleanalytics.getDownloadExtension(base_url + Drupal.settings.basePath + '/files/foo2.dddd'), "No download extension found in '" + base_url + Drupal.settings.basePath + "files/foo2.dddd'."); + console.groupEnd(); + + // List of top-level domains: example.com, example.net + console.group("Test 'isCrossDomain' (requires cross domain configuration with 'example.com' and 'example.net'):"); + if (Drupal.settings.googleanalytics.trackCrossDomains) { + console.dir(Drupal.settings.googleanalytics.trackCrossDomains); + Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isCrossDomain('example.com', Drupal.settings.googleanalytics.trackCrossDomains), "URL 'example.com' has been found in cross domain list."); + Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isCrossDomain('example.net', Drupal.settings.googleanalytics.trackCrossDomains), "URL 'example.net' has been found in cross domain list."); + Drupal.googleanalytics.test.assertFalse(Drupal.googleanalytics.isCrossDomain('www.example.com', Drupal.settings.googleanalytics.trackCrossDomains), "URL 'www.example.com' not found in cross domain list."); + Drupal.googleanalytics.test.assertFalse(Drupal.googleanalytics.isCrossDomain('www.example.net', Drupal.settings.googleanalytics.trackCrossDomains), "URL 'www.example.net' not found in cross domain list."); + } + else { + console.warn('Cross domain tracking is not enabled. Tests skipped.'); + } + console.groupEnd(); + +}); + +})(jQuery); diff --git a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.variable.inc b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.variable.inc index 36885de..5b88b1f 100644 --- a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.variable.inc +++ b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.variable.inc @@ -13,10 +13,11 @@ function googleanalytics_variable_info($options) { 'type' => 'string', 'title' => t('Web Property ID', array(), $options), 'default' => 'UA-', - 'description' => t('This ID is unique to each site you want to track separately, and is in the form of UA-xxxxxxx-yy. To get a Web Property ID, register your site with Google Analytics, or if you already have registered your site, go to your Google Analytics Settings page to see the ID next to every site profile. Find more information in the documentation.', array('@analytics' => 'http://www.google.com/analytics/', '@webpropertyid' => url('https://developers.google.com/analytics/resources/concepts/gaConceptsAccounts', array('fragment' => 'webProperty'))), $options), + 'description' => t('This ID is unique to each site you want to track separately, and is in the form of UA-xxxxxxx-yy. To get a Web Property ID, register your site with Google Analytics, or if you already have registered your site, go to your Google Analytics Settings page to see the ID next to every site profile. Find more information in the documentation.', array('@analytics' => 'https://marketingplatform.google.com/about/analytics/', '@webpropertyid' => url('https://developers.google.com/analytics/resources/concepts/gaConceptsAccounts', array('fragment' => 'webProperty'))), $options), 'required' => TRUE, 'group' => 'googleanalytics', 'localize' => TRUE, + 'multidomain' => TRUE, 'validate callback' => 'googleanalytics_validate_googleanalytics_account', ); @@ -39,12 +40,16 @@ function googleanalytics_variable_group_info() { /** * Validate Web Property ID variable. + * + * @param array $variable + * + * @return string */ function googleanalytics_validate_googleanalytics_account($variable) { // Replace all type of dashes (n-dash, m-dash, minus) with the normal dashes. $variable['value'] = str_replace(array('–', '—', '−'), '-', $variable['value']); - if (!preg_match('/^UA-\d{4,}-\d+$/', $variable['value'])) { + if (!preg_match('/^UA-\d+-\d+$/', $variable['value'])) { return t('A valid Google Analytics Web Property ID is case sensitive and formatted like UA-xxxxxxx-yy.'); } } diff --git a/sites/all/modules/contrib/admin/module_filter/CHANGELOG.txt b/sites/all/modules/contrib/admin/module_filter/CHANGELOG.txt index 4320cc1..5513831 100644 --- a/sites/all/modules/contrib/admin/module_filter/CHANGELOG.txt +++ b/sites/all/modules/contrib/admin/module_filter/CHANGELOG.txt @@ -1,41 +1,193 @@ -Module Filter 7.x-1.7, 2012-07-05 +Module Filter 7.x-2.1, 2017-06-09 --------------------------------- +Issue #2437439 by mikhail.krainiuk, greenSkin, jayhawkfan75: Module Filter does + not care about anchors in permission links. +Issue #2866236 by Munavijayalakshmi, dhruveshdtripathi: Typo error in + README.TXT file. +Issue #2452067 by Madis: Option to show description expanded as default + not working. +Issue #2580791 by makbul_khan8: Coding standards and few function without + help comments. +Issue #2153697 by annya: Disabling option "Number of enabled modules" breaks + tabs functionality. +Issue #1710230 by willvincent: On | Off buttons does not change state with + jquery_update module active. +Added option to show description as expanded by default. +Improved description field so when it is open, interacting with it's contents + does not make it collapse. +Placed collapsed/expanded images inside of module for easier, more reliable + access. +Added option to place version in own column. +Issue #2113191 by joelpittet: Category tabs not working. + + +Module Filter 7.x-2.0, 2015-02-22 +--------------------------------- +Simplifying the table rows by hiding version and requirements until a + particular description is clicked. +#2235553 by greenSkin: Fixed latest jquery_update breaks module filter. +#2304687 by mpdonadio: Fixed Remove hardcoded operations. +#2293029 by topsitemakers: Fixed Take header offset into account when selecting + a tab. +#2290213 by topsitemakers: Minor typo in description - "has no affect" -> "has + no effect". +#2141743, #2141743 by greenSkin: Fixed issues related to the new dynamically + positioned tabs and using the dynamically positioned save button. +by greenSkin: Tabs now should always be visible while scrolling large lists of + modules. +#1854348 by alexweber, greenSkin: Make filter textfield wider on modules page + when viewing as tabs. +by greenSkin: Fixed what was suppose to be a call to variable_set(). +#1370492 by greenSkin: Remember selected tab after modules form submit. +by greenSkin: Changed the title for the 'module_filter_dynamic_save_position' + checkbox to make it clearer what it does. +#1166414 by greenSkin: Fixed broken submit when tabs are disabled. We no longer + reroute the theme of the system modules page unless tabs are enabled. +by greenSkin: Fixed issue on Available updates page where the update state + would not be remembered. +by greenSkin: Fixed 7200 update to rebuild the theme registry at the same time + as rebuilding the menu. +by greenSkin: Fixed issue relating to row coloring when enabling/disabling + modules via switch due to recent fix for jQuery Update module. +by greenSkin: Added functionality to show recently enabled/disabled modules. +#1710230 by littlekoala, greenSkin: Fixed On | Off buttons does not change + state with jquery_update() module active. +by greenSkin: Added description to filter textfield on permissions page. +by greenSkin: Added filter to user permissions page. +by greenSkin: Fixed styling when JavaScript is disabled. +by greenSkin: Fixed compatiblility with page_actions. +#1351184 klonos, greenSkin: Added support for update_advanced "Ignored from + settings". +#1149978 by good_man, greenSkin: Added RTL Styling for tabs and toggle switch. +by greenSkin: Hide toggle switch when JavaScript is disabled. +by greenSkin: Added support for ctools dropbutton as well as views styling for + ctools dropbutton. +by greenSkin: Improved hash validation. +by greenSkin: Added ability for tabs to be disabled. Direct use case is when + the "New" tab contains zero new modules. +by greenSkin: Added title to "New" tab link that helps to describe the criteria + of a "new" module. +#1320796 by greenSkin: Added some validation checks before trying to select a + tab in case the tab does not actually exist. +#1429248 by klonos, greenSkin: Fixed Modules page table header overlaps + admin_menu(). +#1494694 by greenSkin: Added Let me decide if I want the cursor to focus on the + search box or not. +#1515256 by catmat, greenSkin: Fixed Tabbed theme may remove functional + content. +by greenSkin: Integrated dynamic positioning of the save button back in, but + if the module page_actions is enabled we let it handle the save button. See + issue #1424994. +by greenSkin: Clicking on module name now affects the toggle switch. +by greenSkin: Do not render the switch for incompatible modules. #1033012 by greenSkin: Hide incompatible module rows when the 'Unavailable' checkbox is unchecked. -#1170388 by greenSkin: Fixed conflict with Overlay module. Added class - "overlay-exclude" to tab links. +by greenSkin: Performance tweak to counting the number of enabled modules. +by greenSkin: Base new modules on the filectime of their .info file. +#1424034 by greenSkin: Now adds the jquery.cookie.js file when needed. +by greenSkin: Removed tweaks to the save configuration button in favor + http://drupal.org/project/page_actions. +by greenSkin: Updated hook_uninstall to del all Module Filter variables. +by greenSkin: No longer including machine name in the tab summary of modules to + enable/disable. +by greenSkin: Made switches honor disabled checkboxes. A disabled switch can + not be turned on or off. +by greenSkin: Added setting to toggle between using the switch or checkbox for + enabling/disabling modules. +by greenSkin: Centered enable switch. +by greenSkin: Moved couple styles to be applied via JavaScript instead of CSS + so they get applied once the initial loading has finished. +by greenSkin: Implemented a "switch" look instead of checkboxes. +by greenSkin: Remember last selected state on "Available updates" page. +by greenSkin: Switched to using filectime() rather than filemtime() for + determining new modules. +#1354134 by klonos, greenSkin: Module list now formats correctly in core + "Garland" theme. +#1354134 by klonos, greenSkin: Module list now formats correctly in core + "Garland" theme. #1124218 by jyee, greenSkin: Suppress form submission when hitting the enter key while the filter input is focused. - - -Module Filter 7.x-1.6, 2011-09-15 ---------------------------------- -#1241662 by Niklas Fiekas: Sort modules by display name. +by greenSkin: Simplified filter rules for updates page. Instead of checkboxes, + now using radios. +by greenSkin: Fixed "All" tab to be selected by default when the page is first + loaded. +#1350124 by greenSkin: Fixed filtering on package tab. +by greenSkin: Added the "All" tab back. Added a "New" tab that lists modules + installed within the last week. +by greenSkin: Fixed "No Results" not showing within selected tabs. +#1259876 by greenSkin: Filter criteria remembered after save. +by greenSkin: Remove deprecated function moduleGetID() from JavaScript code. +by greenSkin: Filter now uses OR instead of AND when filtering multiply + queries. +#1288590 by greenSkin: Fixed Drupal.settings.moduleFilter.enabledCounts[id] is + undefined. +#1344214 by eMPee584: Fixed Notice: Undefined index: #default_value in + theme_module_filter_system_modules_tabs(). +#1170388 by greenSkin: Fixed confict with Overlay module. Added class + "overlay-exclude" to tab links. Added setting to toggle the use of a URL + fragment when selecting tabs. +by greenSkin: Added README.txt. +by greenSkin: Fixed table row striping. +by greenSkin: Fixed regular expression to not require an operator be at the + beginning. Make filter queries filter with AND instead of OR. Each query will + further filter the list rather than potentially add to it. +by greenSkin: Spruced up the regular expression for splitting query strings. +by greenSkin: Added "description:" operator. +by greenSkin: Added operator support to filter. Added "requires:" and + "requiredBy:" operators. +by greenSkin: Filter now processes multiple queries separated by spaces. Use + quotes for a single query that includes space(s). +by greenSkin: Fixed not updating the index when a module is enabled/disabled. +by greenSkin: Fixed visual aid for enabling/disabling modules. Previously had + failed to remove +/- from tab summary. +by Kiphaas7, greenSkin: Tabs can now be configured to hide when they contain + no results. +by Kiphaas7, greenSkin: Added result info to tabs. When a filter is performed, + a count per tab is displayed of the number of visible results for that tab. +by greenSkin: Now more descriptive of what modules are being enabled/disabled. +by greenSkin: Moved module operation links to below "Requires" and + "Required by" section. +by greenSkin: Added a suggest class to tabs when their module is hovered. +by greenSkin: Distinguished difference between tab ID and hash. +by greenSkin: Only alter the menu item 'admin/reports/updates' if it first + exists. +by greenSkin: Added update to force a menu rebuild. This is needed to let + Module Filter alter the Update Status menu item in order to provide our + filter on its page. #1254140 by greenSkin: No longer return anything in hook_update_7100. -by greenSkin: Fixed bug with visual aids sometimes not updating correctly. - - -Module Filter 7.x-1.5, 2011-08-16 ---------------------------------- -by greenSkin: Brought the 7.x branch current with the 6.x branch features. - - -Module Filter 7.x-1.3, 2011-03-07 ---------------------------------- -by realityloop: Updated CHANGELOG.txt - - -Module Filter 7.x-1.2, 2011-03-07 ---------------------------------- -by realityloop: Changed placement of Submit buttons for other languages - - -Module Filter 7.x-1.1, 2011-03-07 ---------------------------------- -by realityloop: first commit via git, broke the release somehow :/ - - -Module Filter 7.x-1.0, 2011-01-04 ---------------------------------- -by realityloop: Fixed Undefined index error. -by greenSkin: Removed unused .css and .js files. +#1257860 by greenSkin: Added filter to update status report. +by greenSkin: Moved hiding of the inputs wrapper to css rather than a style + attribute. +by greenSkin: Added missing semi-colons in JavaScript. +by greenSkin: Changed hook comment from using "Implementation of" to + "Implements". +by greenSkin: Fixed showing all modules by default when no hash is present. +by greenSkin: Turned off autocomplete for filter textfield. +by greenSkin: Now using "all" for hash when no tab is selected. +by greenSkin: Implemented visual aids (displays number of modules being + enabled/disabled as well as coloring the modules row accordingly). +by greenSkin: Implemented the enabled count (displays the number of enabled + modules of total for a package. +by greenSkin: Updated tabs setting description. +by greenSkin: Added missing period. +by greenSkin: Made dynamic save position default to on. Updated element title + on admin page and removed "DEVELOPMENTAL" from description. +by greenSkin: Improved fixed-top positioning when toolbar is enabled. +by greenSkin: Added fixed classes for submit button wrapper. +by greenSkin: Changed module-filter-tabs from a class to an id. +by greenSkin: Fixed filter on modules page when tabs are disabled. +by greenSkin: Set min-height to #module-filter-modules. +by greenSkin: Implemented module_filter element and using attached js and css + more. +by greenSkin: Filter input and checkboxes can now have their default values set + based on query params. +by greenSkin: Tabs now use URL fragments. +by greenSkin: Fixed regular expression used to determine tab ID. +by greenSkin: Tabs have been re-written and are functioning. +by greenSkin: Added Module Filter to "Administration" package. +by greenSkin: Improved filtering performance. +by greenSkin: New filter code. +by greenSkin: Initial tab layout modified. Modules are all in one table but + look like they are in packages. All JavaScript has to do on load now is + remove the package name and header rows from tbody then sort the rows. +by greenSkin: Modified the menu item's description. diff --git a/sites/all/modules/contrib/admin/module_filter/README.txt b/sites/all/modules/contrib/admin/module_filter/README.txt new file mode 100644 index 0000000..b03d970 --- /dev/null +++ b/sites/all/modules/contrib/admin/module_filter/README.txt @@ -0,0 +1,107 @@ +Description +----------- +This module provides a method for filtering modules on the modules page as well +as for filtering projects on the update status report. + +The supplied filter is simpler than using your browsers find feature which +searches the entire page. The provided filter will filter modules/projects that +do not meet your input. + +Along with the filter textfield there are additional +checkboxes that help to narrow the search more. The modules page contains four +checkboxes: Enabled, Disabled, Required, and Unavailable. While the first two +are self-explanatory, the latter two can take an explanation. The Required +checkbox affects visibility of modules that are enabled and have other +module(s) that require it also enabled. The Unavailable checkbox affects +visibility of modules that are disabled and depend on module(s) that are +missing. + +The update status report filter also contains four checkboxes: Up-to-Date, +Update available, Security update, and Unknown. These directly affect the +visibility of each project; whether it is up-to-date, there is an update +available, a security update is available, or the status is unknown. + +Installation +------------ +To install this module, do the following: + +1. Extract the tar ball that you downloaded from Drupal.org. + +2. Upload the entire directory and all its contents to your modules directory. + +Configuration +------------- +To enable and configure this module do the following: + +1. Go to Admin -> Modules, and enable Module Filter. + +2. Go to Admin -> Configuration -> User interface -> Module filter, and make + any necessary configuration changes. + +Tabs +---- +By default Module Filter alters the modules page into tabs (Can be disabled on +configuration page). In the tabs view, each package is converted to a vertical +tab rather than a fieldset which greatly increases the ability to browse them. + +There are several benefits to using the tabs view over the standard view for +the modules page. I've listed the key benefits below as well as additional +information that pertains to each. + +1. The increased ease of browsing between packages. + +2. Allows all modules to be listed alphabetically outside of their package, + making it all the easier to find the module by name rather than package it + happens to be in. + +3. The operations for a module are moved within the description column giving + the description more "elbow room". + +4. Filtering is restricted to within the active tab or globally when no tab is + selected. By default no tab is selected which will list all modules. When a + tab is active and you want to get back to the 'all' state click on the + active tab to deselect it. + +5. The number of enabled modules per tab is shown on the active tab. (Can be + disabled on configuration page) + +6. Nice visual aids become available showing what modules are to be + enabled/disabled and the number of matching modules in each tab when + filtering. (Can be disabled on configuration page) + +7. The save configuration button becomes more accessible, either staying at + the bottom of the window when the tabs exceed past the bottom and at the + top when scrolling past the tabs. (Can be disabled on configuration page) + +8. When filtering, tabs that do not contain matches can be hidden. (Can be + enabled on configuration page) + +9. Tab states are remembered like individual pages allowing you to move + forward and backward within your selections via your browsers + forward/backward buttons. + +10. When viewing all modules (no active tab) and mousing over modules it's tab + becomes highlighted to signify which tab it belongs to. + +Filter operators +---------------- +The modules page's filter has three filter operators available. Filter +operators allow alternative filtering techniques. A filter operator is applied +by typing within the filter textfield 'operator:' (where operator is the +operator type) followed immediately with the string to pass to the operator +function (e.g. 'requires:block'). The available operators are: + +description: + Filter based on a module's description. + +requiredBy: + Filter based on what a module is required by. + +requires: + Filter based on what a module requires. + +Multiple filters (or queries) can be applied by space delimiting. For example, +the filter string 'description:ctools views' would filter down to modules with +"ctools" in the description and "views" within the module's name. To pass a +space within a single query wrap it within double quotes (e.g. 'requires:"chaos +tools"' or '"bulk export"'). diff --git a/sites/all/modules/contrib/admin/module_filter/css/dynamic_position.css b/sites/all/modules/contrib/admin/module_filter/css/dynamic_position.css new file mode 100644 index 0000000..bc3b3e7 --- /dev/null +++ b/sites/all/modules/contrib/admin/module_filter/css/dynamic_position.css @@ -0,0 +1,23 @@ +html.js #module-filter-submit { + background-color: #F6F6F6; + width: 239px; + border: 1px solid #ccc; + border-top: 0; +} +html.js #module-filter-submit .form-actions { + text-align: center; + margin: 0; +} +html.js #module-filter-submit input { + margin: 2em 0 1em; +} +html.js #module-filter-submit.fixed { + position: fixed; + border-top: 1px solid #ccc; +} +html.js #module-filter-submit.fixed-top { + top: 0; +} +html.js #module-filter-submit.fixed-bottom { + bottom: 0; +} diff --git a/sites/all/modules/contrib/admin/module_filter/css/module_filter.css b/sites/all/modules/contrib/admin/module_filter/css/module_filter.css index d2353ba..b03bbb7 100644 --- a/sites/all/modules/contrib/admin/module_filter/css/module_filter.css +++ b/sites/all/modules/contrib/admin/module_filter/css/module_filter.css @@ -1,4 +1,26 @@ - +.module-filter-inputs-wrapper { + display: none; +} +.module-filter-clear { + display: inline; + position: relative; +} +.module-filter-clear a { + margin-left: 5px; + font-size: 11px; + position: absolute; +} #module-filter-show-wrapper .form-item { display: inline; } +.module-filter-no-results { + text-align: center; + text-transform: uppercase; + color: #888; +} +#module-filter-modules { + position: relative; +} +#module-filter-modules table { + position: absolute; +} diff --git a/sites/all/modules/contrib/admin/module_filter/css/module_filter_tab-rtl.css b/sites/all/modules/contrib/admin/module_filter/css/module_filter_tab-rtl.css new file mode 100644 index 0000000..b70f6ef --- /dev/null +++ b/sites/all/modules/contrib/admin/module_filter/css/module_filter_tab-rtl.css @@ -0,0 +1,54 @@ +#module-filter-tabs { + float: right; +} +#module-filter-tabs li.selected a, +#module-filter-tabs li.selected a:hover, +#module-filter-tabs li.selected a:focus, +#module-filter-tabs li.selected a:active { + background-color: #fff; + margin-left: -1px; + margin-right: 0; +} +.admin-operations { + float: left; +} +#module-filter-modules { + margin-right: 240px; +} +html.js .toggle-enable { + background-image: -webkit-gradient(linear, 100% 0%, 0% 0%, color-stop(50%, red), color-stop(50%, orange), color-stop(100%, orange)); + background-image: -moz-linear-gradient(right, red 50%, orange 50%, orange 100%); + background-image: linear-gradient(right, red 50%, orange 50%, orange 100%); +} +html.js .toggle-enable.enabled { + background-image: -webkit-gradient(linear, 100% 0%, 0% 0%, color-stop(50%, orange), color-stop(50%, green), color-stop(100%, green)); + background-image: -moz-linear-gradient(right, orange 50%, green 50%, green 100%); + background-image: linear-gradient(right, orange 50%, green 50%, green 100%); +} +html.js .toggle-enable.disabled { + background: #ccc; + border-color: #ddd; + cursor: auto; +} +html.js .toggle-enable.disabled div { + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0%, #FEFEFE), color-stop(100%, #EEEEEE)); + background-image: -moz-linear-gradient(top, #FEFEFE 0%, #EEEEEE 100%); + background-image: linear-gradient(top, #FEFEFE 0%, #EEEEEE 100%); +} +html.js .toggle-enable div { + -webkit-transition: right 0.2s; + -mox-transition: right 0.2s; + -o-transition: right 0.2s; + transition: right 0.2s; +} +html.js .toggle-enable div:before { + content: "ON"; + left: -24px; +} +html.js .toggle-enable div:after { + content: "OFF"; + left: -24px; +} +html.js .toggle-enable.off div { + right: 24px; +} diff --git a/sites/all/modules/contrib/admin/module_filter/css/module_filter_tab.css b/sites/all/modules/contrib/admin/module_filter/css/module_filter_tab.css index 47e7337..fd7d0f0 100644 --- a/sites/all/modules/contrib/admin/module_filter/css/module_filter_tab.css +++ b/sites/all/modules/contrib/admin/module_filter/css/module_filter_tab.css @@ -1,136 +1,259 @@ +.sticky-header { + z-index: 1; +} -#module-filter-wrapper .form-item { - border: 0px none; +#module-filter-tabs { + float: left; +} +#module-filter-tabs ul { + width: 239px; + list-style: none; + list-style-image: none; + background-color: #ddd; + border: 1px solid #ccc; + border-top: none; + margin: 0; + padding: 0; + line-height: 1; +} +#module-filter-tabs li { + background: #eee; + border-top: 1px solid #ccc; + padding: 0; + margin: 0; + min-width: 0; +} +#module-filter-tabs li#new-tab { + margin-bottom: 10px; + border-bottom: 1px solid #ccc; +} +#module-filter-tabs li.disabled, +#module-filter-tabs li#new-tab.disabled { + pointer-events: none; + cursor: default; + background: #ccc; + border-top-color: #bbb; + border-bottom-color: #bbb; +} +#module-filter-tabs li.disabled a, +#module-filter-tabs li.disabled span { + color: #999; +} +#module-filter-tabs li.suggest { + background: #F9F9F9; +} +#module-filter-tabs li a { + display: block; + text-decoration: none; + padding: 10px; +} +#module-filter-tabs li a span.result-info { + float: right; + font-size: 10px; + color: #999; + margin-top: 3px; +} +#module-filter-tabs li a span.visual-aid { + font-size: 8px; +/* float: right;*/ +} +#module-filter-tabs li span.visual-aid { + font-weight: bold; +} +#module-filter-tabs li a span.enabling { + color: green; +} +#module-filter-tabs li a span.disabling { + color: red; +} +#module-filter-tabs li strong { + font-size: 0.923em; +} +#module-filter-tabs li a:hover, +#module-filter-tabs li a:focus { + outline: 1px dotted; + background: #d5d5d5; + text-decoration: none; + outline: 0; +} +#module-filter-tabs li.selected a, +#module-filter-tabs li.selected a:hover, +#module-filter-tabs li.selected a:focus, +#module-filter-tabs li.selected a:active { + background-color: #fff; + margin-right: -1px; +} +#module-filter-tabs li .summary { + display: block; + margin-bottom: 0; + color: #666; + font-size: 0.846em; + padding-top: 0.4em; +} +#module-filter-tabs li .summary .count { + display: none; +} +#module-filter-tabs li.selected .summary .count { + display: block; +} +html.js #module-filter-submit input { + margin: 2em 2em 1em; +} +html.js .module-filter-inputs-wrapper { + text-align: center; +} +html.js .module-filter-inputs-wrapper .form-item { margin: 0; padding: 9px; } -#module-filter-wrapper .form-item:after { -/* display: block;*/ - clear: none; +html.js .module-filter-inputs-wrapper label { + display: inline; } -#module-filter-left { - float: left; - background-color: #F6F6F6; - border: 1px solid #D6DBDE; - margin-right: -1px; - width: 185px; +html.js .module-filter-inputs-wrapper input[name="module_filter[name]"] { + width: 80%; } -#module-filter-left ul { - margin: 0px; - padding: 0px; - list-style: none; +html.js #module-filter-show-wrapper { + margin-bottom: 1em; } -#module-filter-left ul li { - background: #EFEFEF none repeat scroll 0 0;; - border-bottom: 1px solid #D6DBDE; - margin: 0px; - padding: 0px; - list-style-image: none; +html.js #module-filter-modules { + margin-left: 240px; + border: 1px solid #ccc; } -#module-filter-left ul li.active { - margin-right: -1px; - width: 186px; - background-color: #FFFFFF; - position: relative; +#module-filter-modules table { + border-top: none; + border-right: none; + border-left: none; } -#module-filter-left ul li a { - color: #777777; - display: block; - padding: 0.5em; - line-height: 100%; - font-size: 90%; - outline: none; -} -#module-filter-left ul li.active a { - background-color: #FFFFFF; - color: #000000; - font-weight: bold; -} -#module-filter-left ul li a:hover { - background-color: #F6F6F6; - text-decoration: none; -} -#module-filter-left ul li.active a:hover { - background-color: #FFFFFF; -} -#module-filter-left ul li a span.visual-aid { - font-size: 8px; - float: right; -} -#module-filter-left ul li a span.enabling { - color: green; -} -#module-filter-left ul li a span.disabling { - color: red; - margin-left: 5px; -} -#module-filter-left ul li a span.counts { - font-weight: normal; - display: none; - font-size: 0.8em; - color: #333333; - padding: 2px 0 0; -} -#module-filter-left ul li.active a span.counts { - display: block; -} -#module-filter-submit { +html.js #module-filter-modules table { margin: 0; + border-bottom: none; } -#module-filter-submit .form-actions { - text-align: center; +html.js #module-filter-modules table tr, +html.js #module-filter-modules table td { + border-left: 0; + border-right: 0; } -#module-filter-submit input.form-submit { - margin: 1em 0 0; +#module-filter-modules table thead { + display: none; } -#module-filter-submit.fixed { - position: fixed; - background-color: #F6F6F6; - border: 1px solid #D6DBDE; - margin-left: -1px; - width: 185px; +#module-filter-modules table tr.admin-package-title, +#module-filter-modules table tr.admin-package-title td { + border: none !important; + border-top: 1px solid #ccc !important; + background-color: transparent !important; + padding: 10px 0 0; } -#module-filter-submit.fixed-top { - top: 0; +#module-filter-modules table tr.admin-package-title.first, +#module-filter-modules table tr.admin-package-title.first td { + border-top: none !important; } -#module-filter-submit.fixed-bottom { - bottom: 0; +#module-filter-modules table tr.admin-package-header td { + border: 1px solid #ccc; + text-transform: uppercase; + background: #E1E2DC; + font-weight: normal; + padding: 3px 10px; } -#module-filter-right { - display: block; -} -#module-filter-squeeze { - margin-left: 186px; - background-color: #FFFFFF; - border: 1px solid #D6DBDE; - height: auto !important; -} -.form-item-module-filter-name { - text-align: center; -} -.form-item-module-filter-name label { - display: inline; -} -#module-filter-show-wrapper .form-checkboxes { - text-align: center; -} -#module-filter-show-wrapper .form-item:after { - display: inline; -} -table.package { - margin: 1em 0; -} -table.package, -table.package thead, -table.package tbody, -table.package tbody tr, -table.package td:last-child { - border-right: 0 none; - border-left: 0 none; -} -table.package tr.enabling { +#module-filter-modules table tr.enabling { background-color: #dfd; } -table.package tr.disabling { +#module-filter-modules table tr.disabling { background-color: #fcc; } +#module-filter-modules span.module-machine-name { + font-size: 0.9em; + font-weight: normal; +} +.admin-version { + white-space: nowrap; +} +.admin-operations a.module-link { + display: inline; +} + +html.js .toggle-enable { + margin: auto; + position: relative; + width: 50px; + overflow: hidden; + height: 18px; + line-height: 18px; + font-size: 11px; + text-align: center; + cursor: pointer; + border: 1px solid #ccc; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + -khtml-border-radius: 3px; + border-radius: 3px; + -moz-box-shadow: 0 0 10px rgba(0,0,0,0.50) inset; + -webkit-box-shadow: 0 0 10px rgba(0,0,0,0.50) inset; + box-shadow: 0 0 10px rgba(0,0,0,0.50) inset; + background-clip: padding-box; + background-image: -webkit-gradient(linear, 0% 0%, 100% 0%, color-stop(50%, red), color-stop(50%, orange), color-stop(100%, orange)); + background-image: -moz-linear-gradient(left, red 50%, orange 50%, orange 100%); + background-image: linear-gradient(left, red 50%, orange 50%, orange 100%); +} +html.js .toggle-enable.enabled { + background-image: -webkit-gradient(linear, 0% 0%, 100% 0%, color-stop(50%, orange), color-stop(50%, green), color-stop(100%, green)); + background-image: -moz-linear-gradient(left, orange 50%, green 50%, green 100%); + background-image: linear-gradient(left, orange 50%, green 50%, green 100%); +} +html.js .toggle-enable.disabled { + background: #ccc; + border-color: #ddd; + cursor: auto; +} +html.js .toggle-enable div { + position: relative; + color: #777; + width: 26px; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + -khtml-border-radius: 2px; + border-radius: 2px; + background: white; + text-shadow: 1px 1px 0 white; + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0%, #FEFEFE), color-stop(100%, #EAEAEA)); + background-image: -moz-linear-gradient(top, #FEFEFE 0%, #EAEAEA 100%); + background-image: linear-gradient(top, #FEFEFE 0%, #EAEAEA 100%); + -webkit-transition: left 0.2s; + -mox-transition: left 0.2s; + -o-transition: left 0.2s; + transition: left 0.2s; +} +html.js .toggle-enable.disabled div { + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0%, #FEFEFE), color-stop(100%, #EEEEEE)); + background-image: -moz-linear-gradient(top, #FEFEFE 0%, #EEEEEE 100%); + background-image: linear-gradient(top, #FEFEFE 0%, #EEEEEE 100%); +} +html.js .toggle-enable div:after, +html.js .toggle-enable div:before { + color: white; + text-shadow: none; + width: 25px; + position: absolute; + top: 0; + font-size: 9px; + font-weight: bold; +} +html.js .toggle-enable div:before { + content: "OFF"; + left: -24px; +} +html.js .toggle-enable div:after { + content: "ON"; + right: -24px; +} +html.js .toggle-enable.off div { + left: 24px; +} + +#module-filter-tabs.top-fixed { + position: fixed; + top: 0; +} +#module-filter-tabs.bottom-fixed { + position: fixed; + bottom: 0; +} diff --git a/sites/all/modules/contrib/admin/module_filter/css/modules.css b/sites/all/modules/contrib/admin/module_filter/css/modules.css new file mode 100644 index 0000000..0659825 --- /dev/null +++ b/sites/all/modules/contrib/admin/module_filter/css/modules.css @@ -0,0 +1,53 @@ +#system-modules table { + table-layout: fixed; +} +#system-modules th.checkbox { + width: 8%; +} +#system-modules th.name { + width: 25%; +} +#system-modules th.version { + width: 10%; +} +#system-modules th.links { + width: 15%; +} +#system-modules td { + vertical-align: top; +} +#system-modules .expand.inner { + background: transparent url(../images/collapsed.png) left 0.6em no-repeat; + margin-left: -12px; + padding-left: 12px; +} +#system-modules .expanded.expand.inner { + background: transparent url(../images/expanded.png) left 0.6em no-repeat; +} +#system-modules .description { + cursor: pointer; +} +#system-modules .description .inner { + overflow: hidden; /* truncates descriptions if too long */ + text-overflow: ellipsis; + white-space: nowrap; +} +#system-modules .description .inner.expanded > * { + cursor: auto; +} +#system-modules .description .requirements, +#system-modules .description .links { + display: none; +} +#system-modules .description .expanded.inner { + overflow: visible; + white-space: normal; +} +#system-modules .description .expanded .requirements, +#system-modules .description .expanded .links { + display: block; +} +#system-modules .requirements { + padding: 5px 0; + max-width: 490px; +} diff --git a/sites/all/modules/contrib/admin/module_filter/css/update_status.css b/sites/all/modules/contrib/admin/module_filter/css/update_status.css new file mode 100644 index 0000000..c0bee44 --- /dev/null +++ b/sites/all/modules/contrib/admin/module_filter/css/update_status.css @@ -0,0 +1,18 @@ +#module-filter-update-status-form { + float: right; +} +.module-filter-inputs-wrapper label { + display: inline; +} +.module-filter-inputs-wrapper .form-item-module-filter-name { + margin-bottom: 0; + padding-bottom: 0; + text-align: right; +} +#module-filter-show-wrapper .form-item { + padding: 5px; +} +p.module-filter-no-results { + clear: both; + padding-top: 30px; +} diff --git a/sites/all/modules/contrib/admin/module_filter/images/collapsed.png b/sites/all/modules/contrib/admin/module_filter/images/collapsed.png new file mode 100644 index 0000000..91f3fd4 Binary files /dev/null and b/sites/all/modules/contrib/admin/module_filter/images/collapsed.png differ diff --git a/sites/all/modules/contrib/admin/module_filter/images/expanded.png b/sites/all/modules/contrib/admin/module_filter/images/expanded.png new file mode 100644 index 0000000..46f39ec Binary files /dev/null and b/sites/all/modules/contrib/admin/module_filter/images/expanded.png differ diff --git a/sites/all/modules/contrib/admin/module_filter/js/dynamic_position.js b/sites/all/modules/contrib/admin/module_filter/js/dynamic_position.js index 10c66b4..41c5731 100644 --- a/sites/all/modules/contrib/admin/module_filter/js/dynamic_position.js +++ b/sites/all/modules/contrib/admin/module_filter/js/dynamic_position.js @@ -1,33 +1,48 @@ (function($) { - Drupal.behaviors.moduleFilterDynamicPosition = { - attach: function() { - $(window).scroll(function() { + +Drupal.behaviors.moduleFilterDynamicPosition = { + attach: function(context) { + var $window = $(window); + + $('#module-filter-wrapper', context).once('dynamic-position', function() { + // Move the submit button just below the tabs. + $('#module-filter-tabs').append($('#module-filter-submit')); + + var positionSubmit = function() { + var $tabs = $('#module-filter-tabs'); + var $submit = $('#module-filter-submit', $tabs); + // Vertical movement. - var top = $('#module-filter-tabs').offset().top; - var bottom = top + $('#module-filter-tabs').height(); - var windowHeight = $(window).height(); - if (((bottom - windowHeight) > ($(window).scrollTop() - $('#module-filter-submit').height())) && $(window).scrollTop() + windowHeight - $('#module-filter-submit').height() - $('#all-tab').height() > top) { - $('#module-filter-submit').removeClass('fixed-top').addClass('fixed fixed-bottom'); + var bottom = $tabs.offset().top + $tabs.outerHeight(); + if ($submit.hasClass('fixed-bottom')) { + bottom += $submit.height(); } - else if (bottom < $(window).scrollTop()) { - $('#module-filter-submit').removeClass('fixed-bottom').addClass('fixed fixed-top'); + if (bottom >= $window.height() + $window.scrollTop()) { + $submit.addClass('fixed fixed-bottom'); + $tabs.css('padding-bottom', $submit.height()); } else { - $('#module-filter-submit').removeClass('fixed fixed-bottom fixed-top'); + $submit.removeClass('fixed fixed-bottom'); + $tabs.css('padding-bottom', 0); } // Horizontal movement. - if ($('#module-filter-submit').hasClass('fixed-bottom') || $('#module-filter-submit').hasClass('fixed-top')) { - var left = $('#module-filter-tabs').offset().left - $(window).scrollLeft(); - if (left != $('#module-filter-submit').offset().left - $(window).scrollLeft()) { - $('#module-filter-submit').css('left', left); + if ($submit.hasClass('fixed-bottom') || $submit.hasClass('fixed-top')) { + var left = $tabs.offset().left - $window.scrollLeft(); + if (left != $submit.offset().left - $window.scrollLeft()) { + $submit.css('left', left); } } - }); - $(window).trigger('scroll'); - $(window).resize(function() { - $(window).trigger('scroll'); - }); - } + }; + + // Control the positioning. + $window.scroll(positionSubmit); + $window.resize(positionSubmit); + var moduleFilter = $('input[name="module_filter[name]"]').data('moduleFilter'); + moduleFilter.element.bind('moduleFilter:adjustHeight', positionSubmit); + moduleFilter.adjustHeight(); + }); } +}; + })(jQuery); diff --git a/sites/all/modules/contrib/admin/module_filter/js/module_filter.js b/sites/all/modules/contrib/admin/module_filter/js/module_filter.js index 5c807f1..4fcf6e6 100644 --- a/sites/all/modules/contrib/admin/module_filter/js/module_filter.js +++ b/sites/all/modules/contrib/admin/module_filter/js/module_filter.js @@ -1,120 +1,274 @@ - (function ($) { - var moduleFilterTimeOut; - var moduleFilterTextFilter = ''; - Drupal.behaviors.moduleFilter = { - attach: function() { - $("#module-filter-wrapper").show(); - $('input[name="module_filter[name]"]').focus(); - $('input[name="module_filter[name]"]').keyup(function(e) { - switch (e.which) { - case 13: - if (moduleFilterTimeOut) { - clearTimeout(moduleFilterTimeOut); - } +Drupal.ModuleFilter = {}; - moduleFilter(moduleFilterTextFilter); - break; - default: - if (moduleFilterTextFilter != $(this).val()) { - moduleFilterTextFilter = this.value; - if (moduleFilterTimeOut) { - clearTimeout(moduleFilterTimeOut); - } +Drupal.ModuleFilter.explode = function(string) { + var queryArray = string.match(/([a-zA-Z]+\:(\w+|"[^"]+")*)|\w+|"[^"]+"/g); + if (!queryArray) { + queryArray = new Array(); + } + var i = queryArray.length; + while (i--) { + queryArray[i] = queryArray[i].replace(/"/g, ""); + } + return queryArray; +}; - moduleFilterTimeOut = setTimeout('moduleFilter("' + moduleFilterTextFilter + '")', 500); - } - break; +Drupal.ModuleFilter.getState = function(key) { + if (!Drupal.ModuleFilter.state) { + Drupal.ModuleFilter.state = {}; + var cookie = $.cookie('DrupalModuleFilter'); + var query = cookie ? cookie.split('&') : []; + if (query) { + for (var i in query) { + // Extra check to avoid js errors in Chrome, IE and Safari when + // combined with JS like twitter's widget.js. + // See http://drupal.org/node/798764. + if (typeof(query[i]) == 'string' && query[i].indexOf('=') != -1) { + var values = query[i].split('='); + if (values.length === 2) { + Drupal.ModuleFilter.state[values[0]] = values[1]; + } } - }); - $('input[name="module_filter[name]"]').keypress(function(e) { - if (e.which == 13) e.preventDefault(); - }); - - $('#edit-module-filter-show-enabled').change(function() { - moduleFilter($('input[name="module_filter[name]"]').val()); - }); - $('#edit-module-filter-show-disabled').change(function() { - moduleFilter($('input[name="module_filter[name]"]').val()); - }); - $('#edit-module-filter-show-required').change(function() { - moduleFilter($('input[name="module_filter[name]"]').val()); - }); - $('#edit-module-filter-show-unavailable').change(function() { - moduleFilter($('input[name="module_filter[name]"]').val()); - }); + } } } + return Drupal.ModuleFilter.state[key] ? Drupal.ModuleFilter.state[key] : false; +}; - moduleFilter = function(string) { - stringLowerCase = string.toLowerCase(); +Drupal.ModuleFilter.setState = function(key, value) { + var existing = Drupal.ModuleFilter.getState(key); + if (existing != value) { + Drupal.ModuleFilter.state[key] = value; + var query = []; + for (var i in Drupal.ModuleFilter.state) { + query.push(i + '=' + Drupal.ModuleFilter.state[i]); + } + $.cookie('DrupalModuleFilter', query.join('&'), { expires: 7, path: '/' }); + } +}; - $("fieldset table tbody tr td label > strong").each(function(i) { - var $row = $(this).parents('tr'); - var module = $(this).text(); - var moduleLowerCase = module.toLowerCase(); - var $fieldset = $row.parents('fieldset'); +Drupal.ModuleFilter.Filter = function(element, selector, options) { + var self = this; - if (string != '') { - if ($fieldset.hasClass('collapsed')) { - $fieldset.removeClass('collapsed'); + this.element = element; + this.text = $(this.element).val(); + + this.settings = Drupal.settings.moduleFilter; + + this.selector = selector; + + this.options = $.extend({ + delay: 500, + striping: false, + childSelector: null, + empty: Drupal.t('No results'), + rules: new Array() + }, options); + if (this.options.wrapper == undefined) { + this.options.wrapper = $(self.selector).parent(); + } + + // Add clear button. + this.element.after(''); + if (this.text) { + $('.module-filter-clear a', this.element.parent()).removeClass('js-hide'); + } + $('.module-filter-clear a', this.element.parent()).click(function() { + self.element.val(''); + self.text = ''; + delete self.queries; + self.applyFilter(); + self.element.focus(); + $(this).addClass('js-hide'); + return false; + }); + + this.updateQueries = function() { + var queryStrings = Drupal.ModuleFilter.explode(self.text); + + self.queries = new Array(); + for (var i in queryStrings) { + var query = { operator: 'text', string: queryStrings[i] }; + + if (self.operators != undefined) { + // Check if an operator is possibly used. + if (queryStrings[i].indexOf(':') > 0) { + // Determine operator used. + var args = queryStrings[i].split(':', 2); + var operator = args.shift(); + if (self.operators[operator] != undefined) { + query.operator = operator; + query.string = args.shift(); + } } } - if (moduleLowerCase.match(stringLowerCase)) { - if (moduleFilterVisible($('td.checkbox input', $row))) { - if (!$row.is(':visible')) { - $row.show(); - if ($fieldset.hasClass('collapsed')) { - $fieldset.removeClass('collapsed'); - } - if (!$fieldset.is(':visible')) { - $fieldset.show(); - } + query.string = query.string.toLowerCase(); + + self.queries.push(query); + } + + if (self.queries.length <= 0) { + // Add a blank string query. + self.queries.push({ operator: 'text', string: '' }); + } + }; + + this.applyFilter = function() { + self.results = new Array(); + + self.updateQueries(); + + if (self.index == undefined) { + self.buildIndex(); + } + + self.element.trigger('moduleFilter:start'); + + $.each(self.index, function(key, item) { + var $item = item.element; + + for (var i in self.queries) { + var query = self.queries[i]; + if (query.operator == 'text') { + if (item.text.indexOf(query.string) < 0) { + continue; } } else { - $row.hide(); - if ($row.siblings(':visible').html() == null) { - $fieldset.hide(); + var func = self.operators[query.operator]; + if (!(func(query.string, self, item))) { + continue; } } - } - else { - if ($row.is(':visible')) { - $row.hide(); - if ($row.siblings(':visible').html() == null) { - $fieldset.hide(); - } - } - } - }); - } - moduleFilterVisible = function(checkbox) { - if (checkbox.length > 0) { - if ($('#edit-module-filter-show-enabled').is(':checked')) { - if ($(checkbox).is(':checked') && !$(checkbox).is(':disabled')) { + var rulesResult = self.processRules(item); + if (rulesResult !== false) { return true; } } - if ($('#edit-module-filter-show-disabled').is(':checked')) { - if (!$(checkbox).is(':checked') && !$(checkbox).is(':disabled')) { - return true; + + $item.addClass('js-hide'); + }); + + self.element.trigger('moduleFilter:finish', { results: self.results }); + + if (self.options.striping) { + self.stripe(); + } + + if (self.results.length > 0) { + self.options.wrapper.find('.module-filter-no-results').remove(); + } + else { + if (!self.options.wrapper.find('.module-filter-no-results').length) { + self.options.wrapper.append($('

').text(self.options.empty)); + }; + } + }; + + self.element.keyup(function(e) { + switch (e.which) { + case 13: + if (self.timeOut) { + clearTimeout(self.timeOut); } - } - if ($('#edit-module-filter-show-required').is(':checked')) { - if ($(checkbox).is(':checked') && $(checkbox).is(':disabled')) { - return true; + self.applyFilter(); + break; + default: + if (self.text != $(this).val()) { + if (self.timeOut) { + clearTimeout(self.timeOut); + } + + self.text = $(this).val(); + + if (self.text) { + self.element.parent().find('.module-filter-clear a').removeClass('js-hide'); + } + else { + self.element.parent().find('.module-filter-clear a').addClass('js-hide'); + } + + self.element.trigger('moduleFilter:keyup'); + + self.timeOut = setTimeout(self.applyFilter, self.options.delay); } + break; + } + }); + + self.element.keypress(function(e) { + if (e.which == 13) e.preventDefault(); + }); +}; + +Drupal.ModuleFilter.Filter.prototype.buildIndex = function() { + var self = this; + var index = new Array(); + $(this.selector).each(function(i) { + var text = (self.options.childSelector) ? $(self.options.childSelector, this).text() : $(this).text(); + var item = { + key: i, + element: $(this), + text: text.toLowerCase() + }; + for (var j in self.options.buildIndex) { + var func = self.options.buildIndex[j]; + item = $.extend(func(self, item), item); + } + $(this).data('indexKey', i); + index.push(item); + delete item; + }); + this.index = index; +}; + +Drupal.ModuleFilter.Filter.prototype.processRules = function(item) { + var self = this; + var $item = item.element; + var rulesResult = true; + if (self.options.rules.length > 0) { + for (var i in self.options.rules) { + var func = self.options.rules[i]; + rulesResult = func(self, item); + if (rulesResult === false) { + break; } } - if ($('#edit-module-filter-show-unavailable').is(':checked')) { - if (checkbox.length == 0 || ($(checkbox).size() > 0 && !$(checkbox).is(':checked') && $(checkbox).is(':disabled'))) { - return true; - } - } - return false; } + if (rulesResult !== false) { + $item.removeClass('js-hide'); + self.results.push(item); + } + return rulesResult; +}; + +Drupal.ModuleFilter.Filter.prototype.stripe = function() { + var self = this; + var flip = { even: 'odd', odd: 'even' }; + var stripe = 'odd'; + + $.each(self.index, function(key, item) { + if (!item.element.hasClass('js-hide')) { + item.element.removeClass('odd even') + .addClass(stripe); + stripe = flip[stripe]; + } + }); +}; + +$.fn.moduleFilter = function(selector, options) { + var filterInput = this; + filterInput.parents('.module-filter-inputs-wrapper').show(); + if (Drupal.settings.moduleFilter.setFocus) { + filterInput.focus(); + } + if (Drupal.settings.moduleFilter.expandedDescription) { + $('#system-modules td.description .inner.expand').addClass('expanded'); + } + filterInput.data('moduleFilter', new Drupal.ModuleFilter.Filter(this, selector, options)); +}; + })(jQuery); diff --git a/sites/all/modules/contrib/admin/module_filter/js/module_filter_tab.js b/sites/all/modules/contrib/admin/module_filter/js/module_filter_tab.js index 2dc4620..c62b9ef 100644 --- a/sites/all/modules/contrib/admin/module_filter/js/module_filter_tab.js +++ b/sites/all/modules/contrib/admin/module_filter/js/module_filter_tab.js @@ -1,281 +1,559 @@ (function ($) { - Drupal.ModuleFilter = Drupal.ModuleFilter || {}; - Drupal.ModuleFilter.textFilter = ''; - Drupal.ModuleFilter.timeout; - Drupal.ModuleFilter.tabs = {}; - Drupal.ModuleFilter.enabling = {}; - Drupal.ModuleFilter.disabling = {}; - Drupal.behaviors.moduleFilter = { - attach: function() { - // Set the focus on the module filter textfield. - $('input[name="module_filter[name]"]').focus(); +Drupal.ModuleFilter.tabs = {}; +Drupal.ModuleFilter.enabling = {}; +Drupal.ModuleFilter.disabling = {}; - $('#module-filter-squeeze').css('min-height', $('#module-filter-tabs').height()); +Drupal.ModuleFilter.jQueryIsNewer = function() { + if (Drupal.ModuleFilter.jQueryNewer == undefined) { + var v1parts = $.fn.jquery.split('.'); + var v2parts = new Array('1', '4', '4'); - $('#module-filter-left a.project-tab').each(function(i) { - Drupal.ModuleFilter.tabs[$(this).attr('id')] = new Drupal.ModuleFilter.Tab(this); - }); + for (var i = 0; i < v1parts.length; ++i) { + if (v2parts.length == i) { + Drupal.ModuleFilter.jQueryNewer = true; + return Drupal.ModuleFilter.jQueryNewer; + } - // Move anchors to top of tabs. - $('a.anchor', $('#module-filter-left')).remove().prependTo('#module-filter-tabs'); + if (v1parts[i] == v2parts[i]) { + continue; + } + else if (v1parts[i] > v2parts[i]) { + Drupal.ModuleFilter.jQueryNewer = true; + return Drupal.ModuleFilter.jQueryNewer; + } + else { + Drupal.ModuleFilter.jQueryNewer = false; + return Drupal.ModuleFilter.jQueryNewer; + } + } - $('input[name="module_filter[name]"]').keyup(function(e) { - switch (e.which) { - case 13: - if (Drupal.ModuleFilter.timeout) { - clearTimeout(Drupal.ModuleFilter.timeout); - } + if (v1parts.length != v2parts.length) { + Drupal.ModuleFilter.jQueryNewer = false; + return Drupal.ModuleFilter.jQueryNewer; + } - Drupal.ModuleFilter.filter(Drupal.ModuleFilter.textFilter); - break; - default: - if (Drupal.ModuleFilter.textFilter != $(this).val()) { - Drupal.ModuleFilter.textFilter = this.value; - if (Drupal.ModuleFilter.timeout) { - clearTimeout(Drupal.ModuleFilter.timeout); + Drupal.ModuleFilter.jQueryNewer = false; + } + return Drupal.ModuleFilter.jQueryNewer; +}; + +Drupal.behaviors.moduleFilterTabs = { + attach: function(context) { + if (Drupal.settings.moduleFilter.tabs) { + $('#module-filter-wrapper table:not(.sticky-header)', context).once('module-filter-tabs', function() { + var $modules = $('#module-filter-modules'); + var moduleFilter = $('input[name="module_filter[name]"]').data('moduleFilter'); + var table = $(this); + + $('thead', table).show(); + + // Remove package header rows. + $('tr.admin-package-header', table).remove(); + + var $tabsWrapper = $('

'); + + // Build tabs from package title rows. + var tabs = '
    '; + for (var i in Drupal.settings.moduleFilter.packageIDs) { + var id = Drupal.checkPlain(Drupal.settings.moduleFilter.packageIDs[i]); + + var name = id; + var tabClass = 'project-tab'; + var title = null; + var summary = (Drupal.settings.moduleFilter.countEnabled) ? '' + Drupal.ModuleFilter.countSummary(id) + '' : ''; + + switch (id) { + case 'all': + name = Drupal.t('All'); + break; + case 'new': + name = Drupal.t('New'); + title = Drupal.t('Modules installed within the last week.'); + if (Drupal.settings.moduleFilter.enabledCounts['new'].total == 0) { + tabClass += ' disabled'; + summary += '' + Drupal.t('No modules added within the last week.') + ''; } - Drupal.ModuleFilter.timeout = setTimeout('Drupal.ModuleFilter.filter("' + Drupal.ModuleFilter.textFilter + '")', 500); - } - break; - } - }); - $('input[name="module_filter[name]"]').keypress(function(e) { - if (e.which == 13) e.preventDefault(); - }); - - Drupal.ModuleFilter.showEnabled = $('#edit-module-filter-show-enabled').is(':checked'); - $('#edit-module-filter-show-enabled').change(function() { - Drupal.ModuleFilter.showEnabled = $(this).is(':checked'); - Drupal.ModuleFilter.filter($('input[name="module_filter[name]"]').val()); - }); - Drupal.ModuleFilter.showDisabled = $('#edit-module-filter-show-disabled').is(':checked'); - $('#edit-module-filter-show-disabled').change(function() { - Drupal.ModuleFilter.showDisabled = $(this).is(':checked'); - Drupal.ModuleFilter.filter($('input[name="module_filter[name]"]').val()); - }); - Drupal.ModuleFilter.showRequired = $('#edit-module-filter-show-required').is(':checked'); - $('#edit-module-filter-show-required').change(function() { - Drupal.ModuleFilter.showRequired = $(this).is(':checked'); - Drupal.ModuleFilter.filter($('input[name="module_filter[name]"]').val()); - }); - Drupal.ModuleFilter.showUnavailable = $('#edit-module-filter-show-unavailable').is(':checked'); - $('#edit-module-filter-show-unavailable').change(function() { - Drupal.ModuleFilter.showUnavailable = $(this).is(':checked'); - Drupal.ModuleFilter.filter($('input[name="module_filter[name]"]').val()); - }); - - if (Drupal.settings.moduleFilter.visualAid == 1) { - $('table.package tbody td.checkbox input').change(function() { - if ($(this).is(':checked')) { - Drupal.ModuleFilter.updateVisualAid('enable', $(this).parents('tr')); + break; + case 'recent': + name = Drupal.t('Recent'); + title = Drupal.t('Modules enabled/disabled within the last week.'); + if (Drupal.settings.moduleFilter.enabledCounts['recent'].total == 0) { + tabClass += ' disabled'; + summary += '' + Drupal.t('No modules were enabled or disabled within the last week.') + ''; + } + break; + default: + var $row = $('#' + id + '-package', this); + name = Drupal.checkPlain($.trim($row.text())); + $row.remove(); + break; } - else { - Drupal.ModuleFilter.updateVisualAid('disable', $(this).parents('tr')); + + tabs += '
  • ' + name + '' + summary + '
  • '; + } + tabs += '
'; + $tabsWrapper.append(tabs); + $modules.before($tabsWrapper); + + // Index tabs. + $('#module-filter-tabs li').each(function() { + var $tab = $(this); + var id = $tab.attr('id'); + Drupal.ModuleFilter.tabs[id] = new Drupal.ModuleFilter.Tab($tab, id); + }); + + $('tbody td.checkbox input', $modules).change(function() { + var $checkbox = $(this); + var key = $checkbox.parents('tr').data('indexKey'); + + moduleFilter.index[key].status = $checkbox.is(':checked'); + + if (Drupal.settings.moduleFilter.visualAid) { + var type = ($checkbox.is(':checked')) ? 'enable' : 'disable'; + Drupal.ModuleFilter.updateVisualAid(type, $checkbox.parents('tr')); } }); - } - // Check for anchor. - var url = document.location.toString(); - if (url.match('#')) { - // Make tab active based on anchor. - var anchor = '#' + url.split('#')[1]; - $('a[href="' + anchor + '"]').click(); - } - // Else if no active tab is defined, set it to the all tab. - else if (Drupal.ModuleFilter.activeTab == undefined) { - Drupal.ModuleFilter.activeTab = Drupal.ModuleFilter.tabs['all-tab']; + // Sort rows. + var rows = $('tbody tr.module', table).get(); + rows.sort(function(a, b) { + var compA = $('td:nth(1)', a).text().toLowerCase(); + var compB = $('td:nth(1)', b).text().toLowerCase(); + return (compA < compB) ? -1 : (compA > compB) ? 1 : 0; + }); + $.each(rows, function(idx, itm) { table.append(itm); }); + + // Re-stripe rows. + $('tr.module', table) + .removeClass('odd even') + .filter(':odd').addClass('even').end() + .filter(':even').addClass('odd'); + + moduleFilter.adjustHeight(); + + moduleFilter.element.bind('moduleFilter:start', function() { + moduleFilter.tabResults = { + 'all-tab': { items: {}, count: 0 }, + 'recent-tab': { items: {}, count: 0 }, + 'new-tab': { items: {}, count: 0 } + }; + + // Empty result info from tabs. + for (var i in Drupal.ModuleFilter.tabs) { + if (Drupal.ModuleFilter.tabs[i].resultInfo != undefined) { + Drupal.ModuleFilter.tabs[i].resultInfo.empty(); + } + } + }); + + moduleFilter.element.bind('moduleFilter:finish', function(e, data) { + $.each(moduleFilter.index, function(key, item) { + if (!item.element.hasClass('js-hide')) { + var id = Drupal.ModuleFilter.getTabID(item.element); + + if (moduleFilter.tabResults[id] == undefined) { + moduleFilter.tabResults[id] = { items: {}, count: 0 }; + } + if (moduleFilter.tabResults[id].items[item.key] == undefined) { + // All tab + moduleFilter.tabResults['all-tab'].count++; + + // Recent tab + if (item.element.hasClass('recent-module')) { + moduleFilter.tabResults['recent-tab'].count++; + } + + // New tab + if (item.element.hasClass('new-module')) { + moduleFilter.tabResults['new-tab'].count++; + } + + moduleFilter.tabResults[id].items[item.key] = item; + moduleFilter.tabResults[id].count++; + } + + if (Drupal.ModuleFilter.activeTab != undefined && Drupal.ModuleFilter.activeTab.id != 'all-tab') { + if ((Drupal.ModuleFilter.activeTab.id == 'recent-tab' && !item.element.hasClass('recent-module')) || (Drupal.ModuleFilter.activeTab.id == 'new-tab' && !item.element.hasClass('new-module')) || (Drupal.ModuleFilter.activeTab.id != 'recent-tab' && Drupal.ModuleFilter.activeTab.id != 'new-tab' && id != Drupal.ModuleFilter.activeTab.id)) { + // The item is not in the active tab, so hide it. + item.element.addClass('js-hide'); + } + } + } + }); + + if (Drupal.settings.moduleFilter.visualAid) { + if (moduleFilter.text) { + // Add result info to tabs. + for (var id in moduleFilter.tabResults) { + var tab = Drupal.ModuleFilter.tabs[id]; + + if (tab.resultInfo == undefined) { + var resultInfo = '' + $('a', tab.element).prepend(resultInfo); + tab.resultInfo = $('span.result-info', tab.element); + } + + tab.resultInfo.append(moduleFilter.tabResults[id].count); + } + + if (Drupal.settings.moduleFilter.hideEmptyTabs) { + for (var id in Drupal.ModuleFilter.tabs) { + if (moduleFilter.tabResults[id] != undefined) { + Drupal.ModuleFilter.tabs[id].element.show(); + } + else if (Drupal.ModuleFilter.activeTab == undefined || Drupal.ModuleFilter.activeTab.id != id) { + Drupal.ModuleFilter.tabs[id].element.hide(); + } + } + } + } + else { + // Make sure all tabs are visible. + if (Drupal.settings.moduleFilter.hideEmptyTabs) { + $('#module-filter-tabs li').show(); + } + } + } + + if ((Drupal.ModuleFilter.activeTab != undefined && (moduleFilter.tabResults[Drupal.ModuleFilter.activeTab.id] == undefined || moduleFilter.tabResults[Drupal.ModuleFilter.activeTab.id].count <= 0))) { + // The current tab contains no results. + moduleFilter.results = 0; + } + + moduleFilter.adjustHeight(); + }); + + if (Drupal.settings.moduleFilter.useURLFragment) { + $(window).bind('hashchange.module-filter', $.proxy(Drupal.ModuleFilter, 'eventHandlerOperateByURLFragment')).triggerHandler('hashchange.module-filter'); + } + else { + Drupal.ModuleFilter.selectTab(); + } + + if (Drupal.settings.moduleFilter.useSwitch) { + $('td.checkbox div.form-item', table).hide(); + $('td.checkbox', table).each(function(i) { + var $cell = $(this); + var $checkbox = $(':checkbox', $cell); + var $switch = $('.toggle-enable', $cell); + $switch.removeClass('js-hide').click(function() { + if (!$(this).hasClass('disabled')) { + if (Drupal.ModuleFilter.jQueryIsNewer()) { + $checkbox.click(); + $switch.toggleClass('off'); + } + else { + $checkbox.click().change(); + $switch.toggleClass('off'); + } + } + }); + }); + } + + var $tabs = $('#module-filter-tabs'); + + function getParentTopOffset($obj, offset) { + var $parent = $obj.offsetParent(); + if ($obj[0] != $parent[0]) { + offset += $parent.position().top; + return getParentTopOffset($parent, offset); + } + return offset; + } + + var tabsTopOffset = null; + function getParentsTopOffset() { + if (tabsTopOffset === null) { + tabsTopOffset = getParentTopOffset($tabs.parent(), 0); + } + return tabsTopOffset; + } + + function viewportTop() { + var top = $(window).scrollTop(); + return top; + } + + function viewportBottom() { + var top = $(window).scrollTop(); + var bottom = top + $(window).height(); + + bottom -= $('#page-actions').height(); + + return bottom; + } + + function fixToTop(top) { + if ($tabs.hasClass('bottom-fixed')) { + $tabs.css({ + 'position': 'absolute', + 'top': $tabs.position().top - getParentsTopOffset(), + 'bottom': 'auto' + }); + $tabs.removeClass('bottom-fixed'); + } + + if (($tabs.css('position') == 'absolute' && $tabs.offset().top - top >= 0) || ($tabs.css('position') != 'absolute' && $tabs.offset().top - top <= 0)) { + $tabs.addClass('top-fixed'); + $tabs.attr('style', ''); + } + } + + function fixToBottom(bottom) { + if ($tabs.hasClass('top-fixed')) { + $tabs.css({ + 'position': 'absolute', + 'top': $tabs.position().top - getParentsTopOffset(), + 'bottom': 'auto' + }); + $tabs.removeClass('top-fixed'); + } + + if ($tabs.offset().top + $tabs.height() - bottom <= 0) { + $tabs.addClass('bottom-fixed'); + var style = ''; + var pageActionsHeight = $('#page-actions').height(); + if (pageActionsHeight > 0) { + style = 'bottom: ' + pageActionsHeight + 'px'; + } + else if (Drupal.settings.moduleFilter.dynamicPosition) { + // style = 'bottom: ' + $('#module-filter-submit', $tabs).height() + 'px'; + } + $tabs.attr('style', style); + } + } + + var lastTop = 0; + $(window).scroll(function() { + var top = viewportTop(); + var bottom = viewportBottom(); + + if ($modules.offset().top >= top) { + $tabs.removeClass('top-fixed').attr('style', ''); + } + else { + if (top > lastTop) { // Downward scroll. + if ($tabs.height() > bottom - top) { + fixToBottom(bottom); + } + else { + fixToTop(top); + } + } + else { // Upward scroll. + fixToTop(top); + } + } + lastTop = top; + }); + + moduleFilter.adjustHeight(); + }); + } + } +}; + +Drupal.ModuleFilter.Tab = function(element, id) { + var self = this; + + this.id = id; + this.hash = id.substring(0, id.length - 4); + this.element = element; + + $('a', this.element).click(function() { + if (!Drupal.settings.moduleFilter.useURLFragment) { + var hash = (!self.element.hasClass('selected')) ? self.hash : 'all'; + Drupal.ModuleFilter.selectTab(hash); + return false; + } + + if (self.element.hasClass('selected')) { + // Clear the active tab. + window.location.hash = 'all'; + return false; + } + }); + + $('tr.' + this.id, $('#system-modules')).hover( + function() { + self.element.addClass('suggest'); + }, + function() { + self.element.removeClass('suggest'); + } + ); +}; + +Drupal.ModuleFilter.selectTab = function(hash) { + if (!hash || Drupal.ModuleFilter.tabs[hash + '-tab'] == undefined || Drupal.settings.moduleFilter.enabledCounts[hash].total == 0) { + if (Drupal.settings.moduleFilter.rememberActiveTab) { + var activeTab = Drupal.ModuleFilter.getState('activeTab'); + if (activeTab && Drupal.ModuleFilter.tabs[activeTab + '-tab'] != undefined) { + hash = activeTab; } } + + if (!hash) { + hash = 'all'; + } } - Drupal.ModuleFilter.visible = function(checkbox) { - if (checkbox.length > 0) { - if (Drupal.ModuleFilter.showEnabled) { - if ($(checkbox).is(':checked') && !$(checkbox).is(':disabled')) { - return true; - } + if (Drupal.ModuleFilter.activeTab != undefined) { + Drupal.ModuleFilter.activeTab.element.removeClass('selected'); + } + + Drupal.ModuleFilter.activeTab = Drupal.ModuleFilter.tabs[hash + '-tab']; + Drupal.ModuleFilter.activeTab.element.addClass('selected'); + + var moduleFilter = $('input[name="module_filter[name]"]').data('moduleFilter'); + var filter = moduleFilter.applyFilter(); + + if (!Drupal.ModuleFilter.modulesTop) { + Drupal.ModuleFilter.modulesTop = $('#module-filter-modules').offset().top; + } + else { + // Calculate header offset; this is important in case the site is using + // admin_menu module which has fixed positioning and is on top of everything + // else. + var headerOffset = Drupal.settings.tableHeaderOffset ? eval(Drupal.settings.tableHeaderOffset + '()') : 0; + // Scroll back to top of #module-filter-modules. + $('html, body').animate({ + scrollTop: Drupal.ModuleFilter.modulesTop - headerOffset + }, 500); + // $('html, body').scrollTop(Drupal.ModuleFilter.modulesTop); + } + + Drupal.ModuleFilter.setState('activeTab', hash); +}; + +Drupal.ModuleFilter.eventHandlerOperateByURLFragment = function(event) { + var hash = $.param.fragment(); + Drupal.ModuleFilter.selectTab(hash); +}; + +Drupal.ModuleFilter.countSummary = function(id) { + return Drupal.t('@enabled of @total', { '@enabled': Drupal.settings.moduleFilter.enabledCounts[id].enabled, '@total': Drupal.settings.moduleFilter.enabledCounts[id].total }); +}; + +Drupal.ModuleFilter.Tab.prototype.updateEnabling = function(name, remove) { + this.enabling = this.enabling || {}; + if (!remove) { + this.enabling[name] = name; + } + else { + delete this.enabling[name]; + } +}; + +Drupal.ModuleFilter.Tab.prototype.updateDisabling = function(name, remove) { + this.disabling = this.disabling || {}; + if (!remove) { + this.disabling[name] = name; + } + else { + delete this.disabling[name]; + } +}; + +Drupal.ModuleFilter.Tab.prototype.updateVisualAid = function() { + var visualAid = ''; + var enabling = new Array(); + var disabling = new Array(); + + if (this.enabling != undefined) { + for (var i in this.enabling) { + enabling.push(this.enabling[i]); + } + if (enabling.length > 0) { + enabling.sort(); + visualAid += '+' + enabling.join(', ') + ''; + } + } + if (this.disabling != undefined) { + for (var i in this.disabling) { + disabling.push(this.disabling[i]); + } + if (disabling.length > 0) { + disabling.sort(); + if (enabling.length > 0) { + visualAid += '
'; } - if (Drupal.ModuleFilter.showDisabled) { - if (!$(checkbox).is(':checked') && !$(checkbox).is(':disabled')) { - return true; - } - } - if (Drupal.ModuleFilter.showRequired) { - if ($(checkbox).is(':checked') && $(checkbox).is(':disabled')) { - return true; - } - } - } - if (Drupal.ModuleFilter.showUnavailable) { - if (checkbox.length == 0 || (!$(checkbox).is(':checked') && $(checkbox).is(':disabled'))) { - return true; + visualAid += '-' + disabling.join(', ') + ''; + } + } + + if (this.visualAid == undefined) { + $('a span.summary', this.element).append(''); + this.visualAid = $('span.visual-aid', this.element); + } + + this.visualAid.empty().append(visualAid); +}; + +Drupal.ModuleFilter.getTabID = function($row) { + var id = $row.data('moduleFilterTabID'); + if (!id) { + // Find the tab ID. + var classes = $row.attr('class').split(' '); + for (var i in classes) { + if (Drupal.ModuleFilter.tabs[classes[i]] != undefined) { + id = classes[i]; + break; } } + $row.data('moduleFilterTabID', id); + } + return id; +}; + +Drupal.ModuleFilter.updateVisualAid = function(type, $row) { + var id = Drupal.ModuleFilter.getTabID($row); + + if (!id) { return false; } - Drupal.ModuleFilter.filter = function(string) { - var stringLowerCase = string.toLowerCase(); - var flip = 'odd'; - - if (Drupal.ModuleFilter.activeTab.id == 'all-tab') { - var selector = 'table.package tbody tr td label > strong'; - } - else { - var selector = 'table.package tbody tr.' + Drupal.ModuleFilter.activeTab.id + '-content td label > strong'; - } - - $(selector).each(function(i) { - var $row = $(this).parents('tr'); - var module = $(this).text(); - var moduleLowerCase = module.toLowerCase(); - - if (moduleLowerCase.match(stringLowerCase)) { - if (Drupal.ModuleFilter.visible($('td.checkbox :checkbox', $row))) { - $row.removeClass('odd even'); - $row.addClass(flip); - $row.show(); - flip = (flip == 'odd') ? 'even' : 'odd'; - } - else { - $row.hide(); - } + var tab = Drupal.ModuleFilter.tabs[id]; + var name = Drupal.checkPlain($('td:nth(1) strong', $row).text()); + switch (type) { + case 'enable': + if (Drupal.ModuleFilter.disabling[id + name] != undefined) { + delete Drupal.ModuleFilter.disabling[id + name]; + tab.updateDisabling(name, true); + $row.removeClass('disabling'); } else { - $row.hide(); + Drupal.ModuleFilter.enabling[id + name] = name; + tab.updateEnabling(name); + $row.addClass('enabling'); } - }); - } - - Drupal.ModuleFilter.Tab = function(element) { - this.id = $(element).attr('id'); - this.element = element; - - $(this.element).click(function() { - Drupal.ModuleFilter.tabs[$(this).attr('id')].setActive(); - }); - } - - Drupal.ModuleFilter.Tab.prototype.setActive = function() { - if (Drupal.ModuleFilter.activeTab) { - $(Drupal.ModuleFilter.activeTab.element).parent().removeClass('active'); - } - // Assume the default active tab is #all-tab. Remove its active class. - else { - $('#all-tab').parent().removeClass('active'); - } - - Drupal.ModuleFilter.activeTab = this; - $(Drupal.ModuleFilter.activeTab.element).parent().addClass('active'); - Drupal.ModuleFilter.activeTab.displayRows(); - - // Clear filter textfield and refocus on it. - $('input[name="module_filter[name]"]').val(''); - $('input[name="module_filter[name]"]').focus(); - } - - Drupal.ModuleFilter.Tab.prototype.displayRows = function() { - var flip = 'odd'; - var selector = (Drupal.ModuleFilter.activeTab.id == 'all-tab') ? 'table.package tbody tr' : 'table.package tbody tr.' + this.id + '-content'; - $('table.package tbody tr').hide(); - $('table.package tbody tr').removeClass('odd even'); - $(selector).each(function(i) { - if (Drupal.ModuleFilter.visible($('td.checkbox input', $(this)))) { - $(this).addClass(flip); - flip = (flip == 'odd') ? 'even' : 'odd'; - $(this).show(); + break; + case 'disable': + if (Drupal.ModuleFilter.enabling[id + name] != undefined) { + delete Drupal.ModuleFilter.enabling[id + name]; + tab.updateEnabling(name, true); + $row.removeClass('enabling'); } - }); - } - - Drupal.ModuleFilter.Tab.prototype.updateEnabling = function(amount) { - this.enabling = this.enabling || 0; - this.enabling += amount; - if (this.enabling == 0) { - delete(this.enabling); - } - } - - Drupal.ModuleFilter.Tab.prototype.updateDisabling = function(amount) { - this.disabling = this.disabling || 0; - this.disabling += amount; - if (this.disabling == 0) { - delete(this.disabling); - } - } - - Drupal.ModuleFilter.Tab.prototype.updateVisualAid = function() { - var visualAid = ''; - if (this.enabling != undefined) { - visualAid += '' + Drupal.t('+@count', { '@count': this.enabling }) + ''; - } - if (this.disabling != undefined) { - visualAid += '' + Drupal.t('-@count', { '@count': this.disabling }) + ''; - } - - if (!$('span.visual-aid', $(this.element)).size() && visualAid != '') { - $(this.element).prepend(''); - } - - $('span.visual-aid', $(this.element)).empty().append(visualAid); - } - - Drupal.ModuleFilter.updateVisualAid = function(type, row) { - // Find row class. - var classes = row.attr('class').split(' '); - for (var i in classes) { - // Remove '-content' so we can use as id. - var id = classes[i].substring(0, classes[i].length - 8); - if (Drupal.ModuleFilter.tabs[id] != undefined) { - break; + else { + Drupal.ModuleFilter.disabling[id + name] = name; + tab.updateDisabling(name); + $row.addClass('disabling'); } - } - - if (Drupal.ModuleFilter.activeTab.id == 'all-tab') { - var allTab = Drupal.ModuleFilter.activeTab; - var projectTab = Drupal.ModuleFilter.tabs[id]; - } - else { - var allTab = Drupal.ModuleFilter.tabs['all-tab']; - var projectTab = Drupal.ModuleFilter.activeTab; - } - - var name = $('td label strong', row).text(); - switch (type) { - case 'enable': - if (Drupal.ModuleFilter.disabling[id + name] != undefined) { - delete(Drupal.ModuleFilter.disabling[id + name]); - allTab.updateDisabling(-1); - projectTab.updateDisabling(-1); - row.removeClass('disabling'); - } - else { - Drupal.ModuleFilter.enabling[id + name] = name; - allTab.updateEnabling(1); - projectTab.updateEnabling(1); - row.addClass('enabling'); - } - break; - case 'disable': - if (Drupal.ModuleFilter.enabling[id + name] != undefined) { - delete(Drupal.ModuleFilter.enabling[id + name]); - allTab.updateEnabling(-1); - projectTab.updateEnabling(-1); - row.removeClass('enabling'); - } - else { - Drupal.ModuleFilter.disabling[id + name] = name; - allTab.updateDisabling(1); - projectTab.updateDisabling(1); - row.addClass('disabling'); - } - break; - } - - allTab.updateVisualAid(); - projectTab.updateVisualAid(); + break; } + + tab.updateVisualAid(); +}; + +Drupal.ModuleFilter.Filter.prototype.adjustHeight = function() { + // Hack for adjusting the height of the modules section. + var minHeight = $('#module-filter-tabs ul').height() + 10; + minHeight += $('#module-filter-tabs #module-filter-submit').height(); + $('#module-filter-modules').css('min-height', minHeight); + this.element.trigger('moduleFilter:adjustHeight'); +} + })(jQuery); diff --git a/sites/all/modules/contrib/admin/module_filter/js/modules.js b/sites/all/modules/contrib/admin/module_filter/js/modules.js new file mode 100644 index 0000000..b0b4939 --- /dev/null +++ b/sites/all/modules/contrib/admin/module_filter/js/modules.js @@ -0,0 +1,181 @@ +(function($) { + +Drupal.behaviors.moduleFilter = { + attach: function(context) { + $('#system-modules td.description').once('description', function() { + $(this).click(function() { + $('.inner.expand', $(this)).toggleClass('expanded'); + }); + $('.inner.expand', $(this)).children().click(function(e) { + if ($(this).parent().hasClass('expanded')) { + e.stopPropagation(); + } + }); + }); + + $('.module-filter-inputs-wrapper', context).once('module-filter', function() { + var filterInput = $('input[name="module_filter[name]"]', context); + var selector = '#system-modules table tbody tr'; + if (Drupal.settings.moduleFilter.tabs) { + selector += '.module'; + } + + filterInput.moduleFilter(selector, { + wrapper: $('#module-filter-modules'), + delay: 500, + striping: true, + childSelector: 'td:nth(1)', + rules: [ + function(moduleFilter, item) { + if (!item.unavailable) { + if (moduleFilter.options.showEnabled) { + if (item.status && !item.disabled) { + return true; + } + } + if (moduleFilter.options.showDisabled) { + if (!item.status && !item.disabled) { + return true; + } + } + if (moduleFilter.options.showRequired) { + if (item.status && item.disabled) { + return true; + } + } + } + if (moduleFilter.options.showUnavailable) { + if (item.unavailable || (!item.status && item.disabled)) { + return true; + } + } + return false; + } + ], + buildIndex: [ + function(moduleFilter, item) { + var $checkbox = $('td.checkbox :checkbox', item.element); + if ($checkbox.size() > 0) { + item.status = $checkbox.is(':checked'); + item.disabled = $checkbox.is(':disabled'); + } + else { + item.status = false; + item.disabled = true; + item.unavailable = true; + } + return item; + } + ], + showEnabled: $('#edit-module-filter-show-enabled').is(':checked'), + showDisabled: $('#edit-module-filter-show-disabled').is(':checked'), + showRequired: $('#edit-module-filter-show-required').is(':checked'), + showUnavailable: $('#edit-module-filter-show-unavailable').is(':checked') + }); + + var moduleFilter = filterInput.data('moduleFilter'); + + moduleFilter.operators = { + description: function(string, moduleFilter, item) { + if (item.description == undefined) { + var description = $('.description', item.element).clone(); + $('.admin-requirements', description).remove(); + $('.admin-operations', description).remove(); + item.description = description.text().toLowerCase(); + } + + if (item.description.indexOf(string) >= 0) { + return true; + } + }, + requiredBy: function(string, moduleFilter, item) { + if (item.requiredBy == undefined) { + var requirements = Drupal.ModuleFilter.getRequirements(item.element); + item.requires = requirements.requires; + item.requiredBy = requirements.requiredBy; + } + + for (var i in item.requiredBy) { + if (item.requiredBy[i].indexOf(string) >= 0) { + return true; + } + } + }, + requires: function(string, moduleFilter, item) { + if (item.requires == undefined) { + var requirements = Drupal.ModuleFilter.getRequirements(item.element); + item.requires = requirements.requires; + item.requiredBy = requirements.requiredBy; + } + + for (var i in item.requires) { + if (item.requires[i].indexOf(string) >= 0) { + return true; + } + } + } + }; + + $('#edit-module-filter-show-enabled', context).change(function() { + moduleFilter.options.showEnabled = $(this).is(':checked'); + moduleFilter.applyFilter(); + }); + $('#edit-module-filter-show-disabled', context).change(function() { + moduleFilter.options.showDisabled = $(this).is(':checked'); + moduleFilter.applyFilter(); + }); + $('#edit-module-filter-show-required', context).change(function() { + moduleFilter.options.showRequired = $(this).is(':checked'); + moduleFilter.applyFilter(); + }); + $('#edit-module-filter-show-unavailable', context).change(function() { + moduleFilter.options.showUnavailable = $(this).is(':checked'); + moduleFilter.applyFilter(); + }); + + if (!Drupal.settings.moduleFilter.tabs) { + moduleFilter.element.bind('moduleFilter:start', function() { + $('#system-modules fieldset').show(); + }); + + moduleFilter.element.bind('moduleFilter:finish', function(e, data) { + $('#system-modules fieldset').each(function(i) { + $fieldset = $(this); + if ($('tbody tr', $fieldset).filter(':visible').length == 0) { + $fieldset.hide(); + } + }); + }); + + moduleFilter.applyFilter(); + } + }); + } +}; + +Drupal.ModuleFilter.getRequirements = function(element) { + var requires = new Array(); + var requiredBy = new Array(); + $('.admin-requirements', element).each(function() { + var text = $(this).text(); + if (text.substr(0, 9) == 'Requires:') { + // Requires element. + requiresString = text.substr(9); + requires = requiresString.replace(/\([a-z]*\)/g, '').split(','); + } + else if (text.substr(0, 12) == 'Required by:') { + // Required by element. + requiredByString = text.substr(12); + requiredBy = requiredByString.replace(/\([a-z]*\)/g, '').split(','); + } + }); + for (var i in requires) { + requires[i] = $.trim(requires[i].toLowerCase()); + } + for (var i in requiredBy) { + requiredBy[i] = $.trim(requiredBy[i].toLowerCase()); + } + return { requires: requires, requiredBy: requiredBy }; +}; + +})(jQuery); diff --git a/sites/all/modules/contrib/admin/module_filter/js/permissions.js b/sites/all/modules/contrib/admin/module_filter/js/permissions.js new file mode 100644 index 0000000..8340389 --- /dev/null +++ b/sites/all/modules/contrib/admin/module_filter/js/permissions.js @@ -0,0 +1,67 @@ +(function($) { + +var lastModuleItem; + +Drupal.behaviors.moduleFilterPermissions = { + attach: function(context) { + $('.module-filter-inputs-wrapper', context).once('module-filter', function() { + var filterInput = $('input[name="module_filter[name]"]', context); + var selector = '#permissions tbody tr'; + + // Move location of filter input. + $('#permissions').parent().prepend(filterInput.parent().parent()); + + filterInput.moduleFilter(selector, { + wrapper: $('#permissions').parent(), + childSelector: 'td.module', + buildIndex: [ + function(moduleFilter, item) { + item.isModule = (item.text != '') ? true : false; + if (item.isModule) { + item.children = new Array(); + lastModuleItem = item; + } + else { + item.parent = lastModuleItem; + lastModuleItem.children.push(item); + } + return item; + } + ] + }); + + var moduleFilter = filterInput.data('moduleFilter'); + + moduleFilter.operators = { + perm: function(string, moduleFilter, item) { + if (!item.isModule) { + if (item.name == undefined) { + var $name = $('td.permission', item.element).clone(); + $('.description', $name).remove(); + item.name = $name.text().trim().toLowerCase(); + } + + if (item.name.indexOf(string) >= 0) { + return true; + } + } + } + }; + + moduleFilter.element.bind('moduleFilter:finish', function(e, data) { + for (var i in moduleFilter.results) { + if (moduleFilter.results[i].isModule) { + for (var k in moduleFilter.results[i].children) { + moduleFilter.results[i].children[k].element.removeClass('js-hide'); + } + } + else { + moduleFilter.results[i].parent.element.removeClass('js-hide'); + } + } + }); + }); + } +}; + +})(jQuery); \ No newline at end of file diff --git a/sites/all/modules/contrib/admin/module_filter/js/update_status.js b/sites/all/modules/contrib/admin/module_filter/js/update_status.js new file mode 100644 index 0000000..fcd3610 --- /dev/null +++ b/sites/all/modules/contrib/admin/module_filter/js/update_status.js @@ -0,0 +1,117 @@ +(function($) { + +Drupal.behaviors.moduleFilterUpdateStatus = { + attach: function(context) { + $('#module-filter-update-status-form').once('update-status', function() { + var filterInput = $('input[name="module_filter[name]"]', context); + filterInput.moduleFilter('table.update > tbody > tr', { + wrapper: $('table.update:first').parent(), + delay: 300, + childSelector: 'div.project a', + rules: [ + function(moduleFilter, item) { + switch (moduleFilter.options.show) { + case 'all': + return true; + case 'updates': + if (item.state == 'warning' || item.state == 'error') { + return true; + } + break; + case 'security': + if (item.state == 'error') { + return true; + } + break; + case 'ignore': + if (item.state == 'ignored') { + return true; + } + break; + case 'unknown': + if (item.state == 'unknown') { + return true; + } + break; + } + return false; + } + ], + buildIndex: [ + function(moduleFilter, item) { + if ($('.version-status', item.element).text() == Drupal.t('Ignored from settings')) { + item.state = 'ignored'; + return item; + } + if (item.element.is('.ok')) { + item.state = 'ok'; + } + else if (item.element.is('.warning')) { + item.state = 'warning'; + } + else if (item.element.is('.error')) { + item.state = 'error'; + } + else if (item.element.is('.unknown')) { + item.state = 'unknown'; + } + return item; + } + ], + show: $('#edit-module-filter-show input[name="module_filter[show]"]', context).val() + }); + + var moduleFilter = filterInput.data('moduleFilter'); + + if (Drupal.settings.moduleFilter.rememberUpdateState) { + var updateShow = Drupal.ModuleFilter.getState('updateShow'); + if (updateShow) { + moduleFilter.options.show = updateShow; + $('#edit-module-filter-show input[name="module_filter[show]"][value="' + updateShow + '"]', context).click(); + } + } + + $('#edit-module-filter-show input[name="module_filter[show]"]', context).change(function() { + moduleFilter.options.show = $(this).val(); + Drupal.ModuleFilter.setState('updateShow', moduleFilter.options.show); + moduleFilter.applyFilter(); + }); + + moduleFilter.element.bind('moduleFilter:start', function() { + $('table.update').each(function() { + $(this).show().prev('h3').show(); + }); + }); + + moduleFilter.element.bind('moduleFilter:finish', function(e, data) { + $('table.update').each(function() { + var $table = $(this); + if ($('tbody tr', $(this)).filter(':visible').length == 0) { + $table.hide().prev('h3').hide(); + } + }); + }); + + moduleFilter.element.bind('moduleFilter:keyup', function() { + if (moduleFilter.clearOffset == undefined) { + moduleFilter.inputWidth = filterInput.width(); + moduleFilter.clearOffset = moduleFilter.element.parent().find('.module-filter-clear a').width(); + } + if (moduleFilter.text) { + filterInput.width(moduleFilter.inputWidth - moduleFilter.clearOffset - 5).parent().css('margin-right', moduleFilter.clearOffset + 5); + } + else { + filterInput.width(moduleFilter.inputWidth).parent().css('margin-right', 0); + } + }); + + moduleFilter.element.parent().find('.module-filter-clear a').click(function() { + filterInput.width(moduleFilter.inputWidth).parent().css('margin-right', 0); + }); + + moduleFilter.applyFilter(); + }); + } +}; + +})(jQuery); diff --git a/sites/all/modules/contrib/admin/module_filter/module_filter.admin.inc b/sites/all/modules/contrib/admin/module_filter/module_filter.admin.inc index cfdc974..cdb3f49 100644 --- a/sites/all/modules/contrib/admin/module_filter/module_filter.admin.inc +++ b/sites/all/modules/contrib/admin/module_filter/module_filter.admin.inc @@ -14,36 +14,103 @@ * Settings form for module filter. */ function module_filter_settings() { + $form['module_filter_set_focus'] = array( + '#type' => 'checkbox', + '#title' => t('Set focus to filter field on page load'), + '#description' => t('Currently has no effect when using Overlay module.'), + '#default_value' => variable_get('module_filter_set_focus', 1), + ); + $form['module_filter_tabs'] = array( '#type' => 'checkbox', - '#title' => t('Tabs'), - '#description' => t('Divide module groups into tabbed list.'), - '#default_value' => variable_get('module_filter_tabs', 1) + '#title' => t('Enhance the modules page with tabs'), + '#description' => t('Alternate tabbed theme that restructures packages into tabs.'), + '#default_value' => variable_get('module_filter_tabs', 1), ); $form['tabs'] = array( '#type' => 'fieldset', '#title' => t('Tabs'), '#description' => t('Settings used with the tabs view of the modules page.'), '#collapsible' => TRUE, - '#collapsed' => FALSE + '#collapsed' => FALSE, ); $form['tabs']['module_filter_count_enabled'] = array( '#type' => 'checkbox', '#title' => t('Number of enabled modules'), '#description' => t('Display the number of enabled modules in the active tab along with the total number of modules.'), - '#default_value' => variable_get('module_filter_count_enabled', 1) + '#default_value' => variable_get('module_filter_count_enabled', 1), ); $form['tabs']['module_filter_visual_aid'] = array( '#type' => 'checkbox', - '#title' => t('Visuals for newly enabled and disabled modules'), - '#description' => t("Adds a basic count to tabs of modules being enabled/disabled and colors the module row pending it's being enabled or disabled"), - '#default_value' => variable_get('module_filter_visual_aid', 1) + '#title' => t('Visual aids'), + '#description' => t('When enabling/disabling modules, the module name will display in the tab summary.
When filtering, a count of results for each tab will be presented.'), + '#default_value' => variable_get('module_filter_visual_aid', 1), + ); + $form['tabs']['module_filter_hide_empty_tabs'] = array( + '#type' => 'checkbox', + '#title' => t('Hide tabs with no results'), + '#description' => t('When a filter returns no results for a tab, the tab is hidden. This is dependent on visual aids being enabled.'), + '#default_value' => variable_get('module_filter_hide_empty_tabs', 0), ); $form['tabs']['module_filter_dynamic_save_position'] = array( '#type' => 'checkbox', - '#title' => t('Save dynamic positioning'), - '#description' => t("DEVELOPMENTAL: For sites with lots of tabs, enable to help keep the 'Save configuration' button more accessible."), - '#default_value' => variable_get('module_filter_dynamic_save_position', 0) + '#title' => t('Dynamically position Save button'), + '#description' => t("For sites with lots of tabs, enable to help keep the 'Save configuration' button more accessible."), + '#default_value' => variable_get('module_filter_dynamic_save_position', 1), ); + $form['tabs']['module_filter_use_url_fragment'] = array( + '#type' => 'checkbox', + '#title' => t('Use URL fragment'), + '#description' => t('Use URL fragment when navigating between tabs. This lets you use the browsers back/forward buttons to navigate through the tabs you selected.') . '
' . t('When the Overlay module is enabled this functionality will not be used since overlay relies on the URL fragment.'), + '#default_value' => variable_get('module_filter_use_url_fragment', 1), + ); + $form['tabs']['module_filter_use_switch'] = array( + '#type' => 'checkbox', + '#title' => t('Use switch instead of checkbox'), + '#description' => t('This is purely cosmetic (at least for now). Displays a ON/OFF switch rather than a checkbox to enable/disable modules.
Modules will not actually be enabled/disabled until the form is saved.'), + '#default_value' => variable_get('module_filter_use_switch', 1), + ); + $form['tabs']['module_filter_track_recent_modules'] = array( + '#type' => 'checkbox', + '#title' => t('Track recently enabled/disabled modules'), + '#description' => t('Adds a "Recent" tab that displays modules that have been enabled or disabled with the last week.'), + '#default_value' => variable_get('module_filter_track_recent_modules', 1), + ); + $form['tabs']['module_filter_remember_active_tab'] = array( + '#type' => 'checkbox', + '#title' => t('Remember active tab.'), + '#description' => t('When enabled, the active tab will be remembered.'), + '#default_value' => variable_get('module_filter_remember_active_tab', 1), + ); + $form['tabs']['module_filter_version_column'] = array( + '#type' => 'checkbox', + '#title' => t('Place version in own column'), + '#description' => t("Moves the version out of the description and into it's own column"), + '#default_value' => variable_get('module_filter_version_column', 0), + ); + $form['tabs']['module_filter_expanded_description'] = array( + '#type' => 'checkbox', + '#title' => t('Expand description by default'), + '#description' => t('When enabled, the description will be expanded by default.'), + '#default_value' => variable_get('module_filter_expanded_description', 0), + ); + + $form['update'] = array( + '#type' => 'fieldset', + '#title' => t('Update status'), + '#collapsible' => TRUE, + '#collapsed' => (module_exists('update')) ? FALSE : TRUE, + ); + $form['update']['module_filter_remember_update_state'] = array( + '#type' => 'checkbox', + '#title' => t('Remember the last selected filter.'), + '#description' => t('When enabled, the last state (All, Update available, Security update, Unknown) will be remembered.'), + '#default_value' => variable_get('module_filter_remember_update_state', 0), + ); + + if (module_exists('page_actions')) { + $form['tabs']['module_filter_dynamic_save_position']['#description'] .= '
' . t('The module %name is enabled and thus this setting will have no affect.', array('%name' => t('Page actions'))); + } + return system_settings_form($form); } diff --git a/sites/all/modules/contrib/admin/module_filter/module_filter.info b/sites/all/modules/contrib/admin/module_filter/module_filter.info index 86367cd..e21267d 100644 --- a/sites/all/modules/contrib/admin/module_filter/module_filter.info +++ b/sites/all/modules/contrib/admin/module_filter/module_filter.info @@ -1,9 +1,9 @@ name = Module filter description = "Filter the modules list." core = 7.x +package = Administration files[] = module_filter.install -files[] = module_filter.js files[] = module_filter.module files[] = module_filter.admin.inc files[] = module_filter.theme.inc @@ -16,9 +16,8 @@ files[] = js/module_filter_tab.js configure = admin/config/user-interface/modulefilter -; Information added by drupal.org packaging script on 2012-07-05 -version = "7.x-1.7" +; Information added by Drupal.org packaging script on 2019-03-27 +version = "7.x-2.2" core = "7.x" project = "module_filter" -datestamp = "1341518501" - +datestamp = "1553698385" diff --git a/sites/all/modules/contrib/admin/module_filter/module_filter.install b/sites/all/modules/contrib/admin/module_filter/module_filter.install index c06cfd8..529d836 100644 --- a/sites/all/modules/contrib/admin/module_filter/module_filter.install +++ b/sites/all/modules/contrib/admin/module_filter/module_filter.install @@ -5,13 +5,22 @@ */ /** - * Implementation of hook_uninstall(). + * Implements hook_uninstall(). */ function module_filter_uninstall() { + variable_del('module_filter_set_focus'); variable_del('module_filter_tabs'); variable_del('module_filter_count_enabled'); variable_del('module_filter_visual_aid'); + variable_del('module_filter_hide_empty_tabs'); variable_del('module_filter_dynamic_save_position'); + variable_del('module_filter_use_url_fragment'); + variable_del('module_filter_use_switch'); + variable_del('module_filter_track_recent_modules'); + variable_del('module_filter_remember_active_tab'); + variable_del('module_filter_remember_update_state'); + variable_del('module_filter_version_column'); + variable_del('module_filter_expanded_description'); } /** @@ -20,3 +29,21 @@ function module_filter_uninstall() { function module_filter_update_7100() { variable_del('module_filter_autocomplete'); } + +/** + * Rebuild the menu and theme registry. + */ +function module_filter_update_7200() { + menu_rebuild(); + system_rebuild_theme_data(); + drupal_theme_rebuild(); +} + +/** + * Old update that use to remove the module_filter_dynamic_save_position variable. + */ +function module_filter_update_7201() { + // We don't want to remove this update hook but at the same time we no + // longer want to lose the variable setting, so we just comment it out. + // variable_del('module_filter_dynamic_save_position'); +} diff --git a/sites/all/modules/contrib/admin/module_filter/module_filter.module b/sites/all/modules/contrib/admin/module_filter/module_filter.module index 1ce5a39..17059ec 100644 --- a/sites/all/modules/contrib/admin/module_filter/module_filter.module +++ b/sites/all/modules/contrib/admin/module_filter/module_filter.module @@ -12,34 +12,46 @@ */ /** - * Implementation of hook_perm(). + * Implements hook_perm(). */ function module_filter_permission() { return array( 'administer module filter' => array( 'title' => t('Administer Module Filter'), - 'description' => t('Configure how Module Filter performs.') - ) + 'description' => t('Configure how Module Filter performs.'), + ), ); } /** - * Implementation of hook_menu(). + * Implements hook_menu(). */ function module_filter_menu() { $items['admin/config/user-interface/modulefilter'] = array( 'title' => 'Module filter', - 'description' => 'Configure settings for Module Filter.', + 'description' => 'Configure how the modules page looks and acts.', 'access arguments' => array('administer module filter'), 'page callback' => 'drupal_get_form', 'page arguments' => array('module_filter_settings'), - 'file' => 'module_filter.admin.inc' + 'file' => 'module_filter.admin.inc', ); return $items; } /** - * Implementation of hook_form_FORM_ID_alter(). + * Implements hook_menu_alter(). + */ +function module_filter_menu_alter(&$items) { + if (isset($items['admin/reports/updates'])) { + // We route the updates report page through us. + $items['admin/reports/updates']['page callback'] = 'module_filter_update_status'; + $items['admin/reports/updates']['file'] = 'module_filter.pages.inc'; + $items['admin/reports/updates']['module'] = 'module_filter'; + } +} + +/** + * Implements hook_form_FORM_ID_alter(). */ function module_filter_form_system_modules_alter(&$form, &$form_state, $form_id) { // Don't alter the form when confirming. @@ -48,102 +60,237 @@ function module_filter_form_system_modules_alter(&$form, &$form_state, $form_id) } $form['module_filter'] = array( - '#tree' => TRUE, - '#weight' => -1 + '#type' => 'module_filter', + '#attached' => array( + 'js' => array( + drupal_get_path('module', 'module_filter') . '/js/modules.js' => array('weight' => 1), + ), + ), ); - $form['module_filter']['name'] = array( - '#type' => 'textfield', - '#title' => t('Filter list') + $checkbox_defaults = array( + ((isset($_GET['enabled'])) ? $_GET['enabled'] : 1) ? 'enabled' : '', + ((isset($_GET['disabled'])) ? $_GET['disabled'] : 1) ? 'disabled' : '', + ((isset($_GET['required'])) ? $_GET['required'] : 1) ? 'required' : '', + ((isset($_GET['unavailable'])) ? $_GET['unavailable'] : 1) ? 'unavailable' : '', ); - $form['module_filter']['show'] = array( '#type' => 'checkboxes', - '#default_value' => array('enabled', 'disabled', 'required', 'unavailable'), + '#default_value' => array_filter($checkbox_defaults), '#options' => array('enabled' => t('Enabled'), 'disabled' => t('Disabled'), 'required' => t('Required'), 'unavailable' => t('Unavailable')), '#prefix' => '
', - '#suffix' => '
' + '#suffix' => '
', ); if (variable_get('module_filter_tabs', 1)) { - // Remove the fieldsets for each package since we will be using tabs - // instead. Put all modules into one array. - $modules = array( - '#theme' => 'module_filter_modules_table', - '#header' => array( - array('data' => t('Enabled'), 'class' => 'checkbox'), - t('Name'), - t('Version'), - t('Description'), - array('data' => t('Operations'), 'colspan' => 3) - ) - ); + $form['module_filter']['#attached']['css'][] = drupal_get_path('module', 'module_filter') . '/css/module_filter_tab.css'; + $form['module_filter']['#attached']['library'][] = array('system', 'jquery.bbq'); + $form['module_filter']['#attached']['js'][drupal_get_path('module', 'module_filter') . '/js/module_filter_tab.js'] = array('weight' => 2); - $all = t('All'); - $tab_counts = array($all => array('id' => 'all', 'enabled' => 0, 'total' => 0)); - $form['#packages'] = array(); - foreach (element_children($form['modules']) as $package) { - // Add the package to $form['#packages']. Tabs are built from this. - $form['#packages'][$package] = $package; - - if (!isset($tab_counts[$package])) { - $tab_counts[$package] = array('enabled' => 0, 'total' => 0); - } - - foreach (element_children($form['modules'][$package]) as $module) { - $tab_counts[$all]['total']++; - $tab_counts[$package]['total']++; - if (!empty($form['modules'][$package][$module]['enable']['#default_value'])) { - $tab_counts[$all]['enabled']++; - $tab_counts[$package]['enabled']++; - } - - $modules[$module] = $form['modules'][$package][$module]; - $modules[$module]['#package'] = $package; - $modules[$module]['#parents'] = array('modules', $package, $module); - } + if (!module_exists('page_actions') && variable_get('module_filter_dynamic_save_position', 1)) { + $form['module_filter']['#attached']['css'][] = drupal_get_path('module', 'module_filter') . '/css/dynamic_position.css'; + $form['module_filter']['#attached']['js'][drupal_get_path('module', 'module_filter') . '/js/dynamic_position.js'] = array('weight' => 3); } - // Sort the array of modules alphabetically. - uasort($modules, 'module_filter_sort_modules_by_display_name'); + $form['#attached']['css'][] = drupal_get_path('module', 'module_filter') . '/css/modules.css'; - // Replace the $form['modules'] with our $modules array. - $form['modules'] = $modules; - - // Add our $tab_counts array to the form. - $form['#tab_counts'] = $tab_counts; + $form['#theme'] = 'module_filter_system_modules_tabs'; } - $form['#theme'] = 'module_filter_system_modules'; + $form['#submit'][] = 'module_filter_system_modules_submit_redirect'; + + if (variable_get('module_filter_track_recent_modules', 1)) { + $form['#submit'][] = 'module_filter_system_modules_submit_recent'; + } } /** - * Implementation of hook_theme(). + * Implements hook_form_FORM_ID_alter(). + */ +function module_filter_form_user_admin_permissions_alter(&$form, &$form_state) { + $form['module_filter'] = array( + '#type' => 'module_filter', + '#description' => t('Filter list by module. Use the query operator "perm" to filter by permission, e.g., perm:access.'), + '#attached' => array( + 'js' => array( + drupal_get_path('module', 'module_filter') . '/js/permissions.js', + ), + ), + '#weight' => -100, + ); +} + +/** + * Implements hook_element_info(). + */ +function module_filter_element_info() { + $types['module_filter'] = array( + '#input' => TRUE, + '#process' => array('form_process_module_filter', 'ajax_process_form'), + '#weight' => -1, + '#tree' => TRUE, + '#theme' => 'module_filter', + ); + return $types; +} + +/** + * Implements hook_theme(). */ function module_filter_theme() { return array( - 'module_filter_system_modules' => array( - 'render element' => 'form', - 'file' => 'module_filter.theme.inc' - ), - 'module_filter_modules_table' => array( - 'render element' => 'form', + 'module_filter' => array( + 'render element' => 'element', 'file' => 'module_filter.theme.inc', ), 'module_filter_system_modules_tabs' => array( 'render element' => 'form', - 'file' => 'module_filter.theme.inc' - ) + 'file' => 'module_filter.theme.inc', + ), + 'module_filter_operations' => array( + 'variables' => array('links' => array(), 'dropbutton' => FALSE), + 'file' => 'module_filter.theme.inc', + ), ); } -function module_filter_sort_modules_by_display_name($a, $b) { - if (is_array($a) && is_array($b) && isset($a['#package'], $b['#package'])) { - return strcasecmp($a['name']['#markup'], $b['name']['#markup']); +/** + * Create and add new textfield element. + * + * @param $element + * An associative array containing the properties and children of the + * form actions container. + * @param $form_state + * The $form_state array for the form this element belongs to. + * + * @return + * The processed element. + */ +function form_process_module_filter($element, &$form_state) { + $element['name'] = array( + '#type' => 'textfield', + '#title' => (isset($element['#title'])) ? $element['#title'] : t('Filter list'), + '#default_value' => (isset($element['#default_value'])) ? $element['#default_value'] : ((isset($_GET['filter'])) ? $_GET['filter'] : ''), + '#size' => (isset($element['#size'])) ? $element['#size'] : 45, + '#weight' => (isset($element['#weight'])) ? $element['#weight'] : -10, + '#attributes' => ((isset($element['#attributes'])) ? $element['#attributes'] : array()) + array('autocomplete' => 'off'), + '#attached' => array( + 'css' => array( + drupal_get_path('module', 'module_filter') . '/css/module_filter.css', + ), + 'js' => array( + 'misc/jquery.cookie.js', + drupal_get_path('module', 'module_filter') . '/js/module_filter.js', + array( + 'data' => array( + 'moduleFilter' => array( + 'setFocus' => variable_get('module_filter_set_focus', 1), + 'tabs' => variable_get('module_filter_tabs', 1), + 'countEnabled' => variable_get('module_filter_count_enabled', 1), + 'visualAid' => variable_get('module_filter_visual_aid', 1), + 'hideEmptyTabs' => variable_get('module_filter_hide_empty_tabs', 0), + 'dynamicPosition' => (!module_exists('page_actions')) ? variable_get('module_filter_dynamic_save_position', 1) : FALSE, + 'useURLFragment' => variable_get('module_filter_use_url_fragment', 1), + 'useSwitch' => variable_get('module_filter_use_switch', 1), + 'trackRecent' => variable_get('module_filter_track_recent_modules', 1), + 'rememberActiveTab' => variable_get('module_filter_remember_active_tab', 1), + 'rememberUpdateState' => variable_get('module_filter_remember_update_state', 0), + 'expandedDescription' => variable_get('module_filter_expanded_description', 0), + ), + ), + 'type' => 'setting', + ), + ), + ), + ); + if (isset($element['#description'])) { + $element['name']['#description'] = $element['#description']; } - return 0; + return $element; } +/** + * Form submission handler to filters module list. + */ +function module_filter_system_modules_submit_redirect($form, &$form_state) { + $query = array(); + if (!empty($form_state['values']['module_filter']['name'])) { + $query['filter'] = $form_state['values']['module_filter']['name']; + } + $query['enabled'] = (int) (!empty($form_state['values']['module_filter']['show']['enabled'])); + $query['disabled'] = (int) (!empty($form_state['values']['module_filter']['show']['disabled'])); + $query['required'] = (int) (!empty($form_state['values']['module_filter']['show']['required'])); + $query['unavailable'] = (int) (!empty($form_state['values']['module_filter']['show']['unavailable'])); + + $form_state['redirect'] = array( + 'admin/modules', + array('query' => $query), + ); +} + +/** + * Form submission handler to track recently enabled/disabled modules + */ +function module_filter_system_modules_submit_recent($form, &$form_state) { + $recent_modules = variable_get('module_filter_recent_modules', array()); + + foreach ($form_state['values']['modules'] as $package => $modules) { + foreach ($modules as $key => $module) { + if ($form['modules'][$package][$key]['enable']['#default_value'] != $module['enable']) { + $recent_modules[$key] = REQUEST_TIME; + } + } + } + + variable_set('module_filter_recent_modules', $recent_modules); +} + +/** + * Create list of newly added modules (within a week) + * @return + * An array of newly added modules. + */ +function module_filter_new_modules() { + // Get current list of modules. + $files = system_rebuild_module_data(); + + // Remove hidden modules from display list. + $visible_files = $files; + foreach ($visible_files as $filename => $file) { + if (!empty($file->info['hidden'])) { + unset($visible_files[$filename]); + } + } + + uasort($visible_files, 'system_sort_modules_by_info_name'); + + $new_modules = array(); + foreach ($visible_files as $filename => $module) { + $ctime = filectime(dirname($module->uri) . '/' . $module->name . '.info'); + if (($ctime - strtotime('-1 week')) > 0) { + $new_modules[$filename] = module_filter_get_id($filename); + } + } + return $new_modules; +} + +/** + * Function to replace special characters with hyphen. + * @param string $text + * @return + * String + */ function module_filter_get_id($text) { $id = strtolower($text); - return preg_replace('/([^a-z])([\/( )])*/', '-', $id); + $id = preg_replace('/([^a-z0-9]+)/', '-', $id); + return trim($id, '-'); +} + +/** + * Function to return true/false depending on module changed time and a week timestamp + * @param integer $var + * @return + * Boolean indicating + */ +function module_filter_recent_filter($var) { + return (!($var < REQUEST_TIME - 60 * 60 * 24 * 7)); } diff --git a/sites/all/modules/contrib/admin/module_filter/module_filter.pages.inc b/sites/all/modules/contrib/admin/module_filter/module_filter.pages.inc new file mode 100644 index 0000000..a18fbc5 --- /dev/null +++ b/sites/all/modules/contrib/admin/module_filter/module_filter.pages.inc @@ -0,0 +1,53 @@ + drupal_get_form('module_filter_update_status_form'), + 'update_report' => array( + '#markup' => $update_report, + ), + ); +} + +/** + * Form builder for the module filter form. + */ +function module_filter_update_status_form($form, &$form_state) { + $form['module_filter'] = array( + '#type' => 'module_filter', + '#attached' => array( + 'css' => array( + drupal_get_path('module', 'module_filter') . '/css/update_status.css', + ), + 'js' => array( + drupal_get_path('module', 'module_filter') . '/js/update_status.js', + ), + ), + ); + $form['module_filter']['show'] = array( + '#type' => 'radios', + '#default_value' => (isset($_GET['show']) && in_array($_GET['show'], array('all', 'updates', 'security', 'unknown'))) ? $_GET['show'] : 'all', + '#options' => array('all' => t('All'), 'updates' => t('Update available'), 'security' => t('Security update'), 'unknown' => t('Unknown')), + '#prefix' => '
', + '#suffix' => '
', + ); + if (module_exists('update_advanced')) { + $options = $form['module_filter']['show']['#options']; + $form['module_filter']['show']['#options'] = array_slice($options, 0, 2); + $form['module_filter']['show']['#options']['ignore'] = t('Ignored from settings'); + $form['module_filter']['show']['#options'] = array_merge($form['module_filter']['show']['#options'], array_slice($options, 2)); + } + return $form; +} diff --git a/sites/all/modules/contrib/admin/module_filter/module_filter.theme.inc b/sites/all/modules/contrib/admin/module_filter/module_filter.theme.inc index 1f01792..2d00ab7 100644 --- a/sites/all/modules/contrib/admin/module_filter/module_filter.theme.inc +++ b/sites/all/modules/contrib/admin/module_filter/module_filter.theme.inc @@ -6,119 +6,209 @@ * @author greenSkin */ -/** - * A router theme function. - * - * Appropriately themes the system modules page with alterations and based on - * set variables - * - * @param $form - * An associative array containing the structure of the form. - * - * @return - * An output string. - */ -function theme_module_filter_system_modules($variables) { - $form = $variables['form']; - - drupal_add_css(drupal_get_path('module', 'module_filter') .'/css/module_filter.css'); - - $output = ''; - if (variable_get('module_filter_tabs', 1)) { - drupal_add_css(drupal_get_path('module', 'module_filter') .'/css/module_filter_tab.css'); - drupal_add_js(drupal_get_path('module', 'module_filter') .'/js/module_filter_tab.js'); - - drupal_add_js(array('moduleFilter' => array('visualAid' => variable_get('module_filter_visual_aid', 1))), 'setting'); - - if (variable_get('module_filter_dynamic_save_position', 0)) { - drupal_add_js(drupal_get_path('module', 'module_filter') .'/js/dynamic_position.js'); - } - - $form['module_filter']['#size'] = 45; - $output .= theme('module_filter_system_modules_tabs', $form); - } - else { - drupal_add_js(drupal_get_path('module', 'module_filter') .'/js/module_filter.js'); - $form['module_filter']['#prefix'] = ''; - $output = drupal_render($form['module_filter']); - $form['#theme'] = 'system_modules'; - $output .= drupal_render($form['modules']); - $output .= drupal_render($form['actions']); - } - return $output; -} - -function theme_module_filter_modules_table($variables) { - $form = $variables['form']; - - // Individual table headers. - $rows = array(); - // Iterate through all the modules, which are - // children of this fieldset. - foreach (element_children($form) as $key) { - // Stick it into $module for easier accessing. - $module = $form[$key]; - $row = array(); - unset($module['enable']['#title']); - $row[] = array('class' => array('checkbox'), 'data' => drupal_render($module['enable'])); - $label = '' . drupal_render($module['name']) . ''; - $row[] = drupal_render($module['version']); - // Add the description, along with any modules it requires. - $description = drupal_render($module['description']); - if ($module['#requires']) { - $description .= '
' . t('Requires: !module-list', array('!module-list' => implode(', ', $module['#requires']))) . '
'; - } - if ($module['#required_by']) { - $description .= '
' . t('Required by: !module-list', array('!module-list' => implode(', ', $module['#required_by']))) . '
'; - } - $row[] = array('data' => $description, 'class' => array('description')); - // Display links (such as help or permissions) in their own columns. - foreach (array('help', 'permissions', 'configure') as $key) { - $row[] = array('data' => drupal_render($module['links'][$key]), 'class' => array($key)); - } - - $id = module_filter_get_id($module['#package']); - $rows[] = array( - 'data' => $row, - 'class' => array($id .'-tab-content') - ); - } - - return theme('table', array('header' => $form['#header'], 'rows' => $rows, 'attributes' => array('class' => array('package')))); +function theme_module_filter($variables) { + $element = $variables['element']; + return '
' . drupal_render_children($element) . '
'; } /** * Theme callback for the modules tabbed form. */ function theme_module_filter_system_modules_tabs($variables) { - $form = $variables['form']; - - $count_enabled = variable_get('module_filter_count_enabled', 1); - - // Display packages. - $all = t('All'); - $all_count = ($count_enabled) ? '' . t('!enabled of !total', array('!enabled' => $form['#tab_counts'][$all]['enabled'], '!total' => $form['#tab_counts'][$all]['total'])) . '' : ''; - $tabs = array('all' => '
  • ' . $all . $all_count . '
  • '); - foreach ($form['#packages'] as $package) { - $id = module_filter_get_id($package); - - $count = ($count_enabled) ? '' . t('!enabled of !total', array('!enabled' => $form['#tab_counts'][$package]['enabled'], '!total' => $form['#tab_counts'][$package]['total'])) . '' : ''; - $tabs[$id] = '
  • ' . $package . $count . '
  • '; + if (module_exists('views_ui')) { + // Hack to get consistent style with views ctools dropbutton. + if (module_load_include('inc', 'views_ui', 'includes/admin')) { + foreach (views_ui_get_admin_css() as $file => $options) { + drupal_add_css($file, $options); + } + } } + $form = $variables['form']; + + if (!module_exists('page_actions')) { + $form['actions']['#prefix'] = '
    '; + $form['actions']['#suffix'] = '
    '; + } + + $header = array( + array('data' => '', 'class' => array('checkbox')), + array('data' => t('Name'), 'class' => array('name')), + array('data' => t('Description'), 'class' => array('description')), + array('data' => t('Links'), 'class' => array('links')), + ); + if (variable_get('module_filter_version_column', 0)) { + array_splice($header, 2, 0, array(array('data' => t('Version'), 'class' => array('version')))); + } + $package_ids = array('all'); + $enabled['all'] = array(); + + if (variable_get('module_filter_track_recent_modules', 1)) { + $recent_modules = array_filter(variable_get('module_filter_recent_modules', array()), 'module_filter_recent_filter'); + // Save the filtered results. + variable_set('module_filter_recent_modules', $recent_modules); + + $package_ids[] = 'recent'; + $enabled['recent'] = array(); + } + + // Determine what modules are new (within a week). + $new_modules = module_filter_new_modules(); + $package_ids[] = 'new'; + $enabled['new'] = array(); + + $rows = array(); + $flip = array('even' => 'odd', 'odd' => 'even'); + foreach (element_children($form['modules']) as $package) { + $package_id = module_filter_get_id($package); + $package_ids[] = $package_id; + + // Package title and header. + $rows[] = array('data' => array(array('data' => '

    ' . $form['modules'][$package]['#title'] . '

    ', 'colspan' => 4)), 'id' => $package_id . '-package', 'class' => array('admin-package-title')); + $rows[] = array('data' => $header, 'class' => array('admin-package-header')); + + $stripe = 'odd'; + $enabled[$package_id] = array(); + foreach (element_children($form['modules'][$package]) as $key) { + $module = &$form['modules'][$package][$key]; + + $is_enabled = isset($module['enable']['#default_value']) ? $module['enable']['#default_value'] : ''; + $enabled['all'][] = $enabled[$package_id][] = $is_enabled; + if (isset($recent_modules[$key])) { + $enabled['recent'][] = $is_enabled; + } + if (isset($new_modules[$key])) { + $enabled['new'][] = $is_enabled; + } + + $row = array(); + + $version = !empty($module['version']['#markup']); + $requires = !empty($module['#requires']); + $required_by = !empty($module['#required_by']); + + $toggle_enable = ''; + if (isset($module['enable']['#type']) && $module['enable']['#type'] == 'checkbox') { + unset($module['enable']['#title']); + $class = ($is_enabled ? 'enabled' : 'off'); + if (!empty($module['enable']['#disabled'])) { + $class .= ' disabled'; + } + $toggle_enable = '
     
    '; + } + $row[] = array('class' => array('checkbox'), 'data' => $toggle_enable . drupal_render($module['enable'])); + + $label = ' array('name'), 'data' => $label . '>' . drupal_render($module['name']) . ' (' . $key . ')'); + + if (variable_get('module_filter_version_column', 0) && $version) { + $row[] = array('class' => array('version'), 'data' => drupal_render($module['version'])); + } + + // Add the description, along with any modules it requires. + if (empty($module['description']['#markup'])) { + $module['description']['#markup'] = '' . t('No description available.') . ''; + } + $description = '' . drupal_render($module['description']) . ''; + if ($requires || $required_by || (!variable_get('module_filter_version_column', 0) && $version)) { + $description .= '
    '; + if (!variable_get('module_filter_version_column', 0) && $version) { + $description .= '
    ' . t('Version: !module-version', array('!module-version' => drupal_render($module['version']))) . '
    '; + } + if ($requires) { + $description .= '
    ' . t('Requires: !module-list', array('!module-list' => implode(', ', $module['#requires']))) . '
    '; + } + if ($required_by) { + $description .= '
    ' . t('Required by: !module-list', array('!module-list' => implode(', ', $module['#required_by']))) . '
    '; + } + $description .= '
    '; + } + $row[] = array('data' => '
    ' . $description . '
    ', 'class' => array('description')); + + $operations = (module_exists('ctools')) ? theme('module_filter_operations', array('links' => $module['links'], 'dropbutton' => TRUE)) : theme('module_filter_operations', array('links' => $module['links'])); + $row[] = array('data' => '', 'class' => array('links')); + + $class = array(module_filter_get_id($package) . '-tab', 'module', $stripe); + if (isset($recent_modules[$key])) { + $class[] = 'recent-module'; + } + if (isset($new_modules[$key])) { + $class[] = 'new-module'; + } + $rows[] = array('data' => $row, 'no_striping' => TRUE, 'class' => $class); + $stripe = $flip[$stripe]; + } + + // Set the package as printed. + $form['modules'][$package]['#printed'] = TRUE; + } + + //Get packages and count number of modules + $enabled_counts = array(); + foreach ($enabled as $package_id => $value) { + $enabled_counts[$package_id] = array( + 'enabled' => count(array_filter($value)), + 'total' => count($value), + ); + } + drupal_add_js(array( + 'moduleFilter' => array( + 'packageIDs' => $package_ids, + 'enabledCounts' => $enabled_counts, + ) + ), 'setting'); + + // Add first and last class to rows. + $rows[0]['class'][] = 'first'; + $rows[count($rows) - 1]['class'][] = 'last'; + $output = '
    '; - $output .= '
    '; - $output .= '
      '. implode($tabs) . '
    '; - $output .= '
    ' . drupal_render($form['actions']) . '
    '; - $output .= '
    ' . drupal_render($form['module_filter']); - $output .= drupal_render($form['modules']) . '
    '; - $output .= '
    '; - $output .= '
    '; + $output .= '
    ' . drupal_render($form['module_filter']); + $output .= theme('table', array('header' => $header, 'rows' => $rows)); $output .= drupal_render_children($form); + $output .= '
    '; + $output .= '
    '; return $output; } + +/** + * Theme function for module filter operations. + * @param $variables + * @return + * HTML for admin status operations. + */ +function theme_module_filter_operations(&$vars) { + $links = &$vars['links']; + $dropbutton = $vars['dropbutton']; + + $operations = array(); + foreach (element_children($links) as $key) { + if ($dropbutton) { + hide($links[$key]); + if (!empty($links[$key]['#href'])) { + $operations[$key] = array( + 'title' => $links[$key]['#title'], + 'href' => $links[$key]['#href'], + ); + if (isset($links[$key]['#options'])) { + $operations[$key] += $links[$key]['#options']; + } + } + } + else { + $data = drupal_render($links[$key]); + if (!empty($data)) { + $operations[$key] = array('data' => $data); + } + } + } + if (!empty($operations)) { + if ($dropbutton) { + return '
    ' . theme('links__ctools_dropbutton', array('title' => t('Operations'), 'links' => $operations, 'attributes' => array('class' => array('links')))) . '
    '; + } + return '
    ' . theme('item_list', array('items' => $operations, 'attributes' => array('class' => array('links', 'inline')))) . '
    '; + } +} diff --git a/sites/all/modules/contrib/dev/ctools/API.txt b/sites/all/modules/contrib/dev/ctools/API.txt index e0441d6..b698b79 100644 --- a/sites/all/modules/contrib/dev/ctools/API.txt +++ b/sites/all/modules/contrib/dev/ctools/API.txt @@ -3,6 +3,9 @@ Current API Version: 2.0.8 Please note that the API version is an internal number and does not match release numbers. It is entirely possible that releases will not increase the API version number, and increasing this number too often would burden contrib module maintainers who need to keep up with API changes. This file contains a log of changes to the API. +API Version 2.0.9 +Changed import permissions to use the new 'use ctools import' permission. + API Version 2.0.8 Introduce ctools_class_add(). Introduce ctools_class_remove(). diff --git a/sites/all/modules/contrib/dev/ctools/CHANGELOG.txt b/sites/all/modules/contrib/dev/ctools/CHANGELOG.txt deleted file mode 100644 index c5bd5e6..0000000 --- a/sites/all/modules/contrib/dev/ctools/CHANGELOG.txt +++ /dev/null @@ -1,82 +0,0 @@ -Current API VERSION: 2.0. See API.txt for more information. - -ctools 7.x-1.x-dev -================== -#1008120: "New custom content" shows empty form if custom content panes module is not enabled. -#999302 by troky: Fix jump menu. Apparently this wasn't actually committed the last time it was committed. -#1065976 by tekante and David_Rothstein: Reset plugin static cache during module enable to prevent stale data from harming export ui. -#1016510 by EclipseGC: Make the taxonomy system page functional. - -ctools 7.x-1.x-alpha2 (05-Jan-2011) -=================================== - -#911396 by alex_b: Prevent notices in export UI. -#919768 by mikey_p: Allow url options to be sent to ctools_ajax_command_url(). -#358953 by cedarm: Allow term context to return lowercase, spaces to dashes versions of terms. -#931434 by EclipseGc: Argument plugin for node revision ID. -#910656: CTools AJAX sample wizard demo "domesticated" checkbox value not stored. -#922442 by EugenMayer, neclimdul and voxpelli: Make sure ctools_include can handle '' or NULL directory. -#919956 by traviss359: Correct example in wizard advanced help. -#942968: Fix taxonomy term access rule with tag term vocabs. -#840344: node add argument had crufty code causing notices. -#944462 by longhairedgit: Invalid character in regex causes rare notice. -#938778 by dereine: Fix profile content type for D7 updates. -Add detach event to modal close so that wysiwyg can detach the editor. -Variant titles showing up as blank if more than one variant on a page. -#940016: token support was not yet updated for D7. -#940446: Skip validation on back and cancel buttons in all wizards. -#954492: Redirect not always working in wizard.inc -#955348: Lack of redirect on "Update" button in Page Manager causing data loss sometimes. -#941778: Update and save button should not appear in the "Add variant" path. -#955070 by EclipseGc: Update ctools internal page tokens to work properly on content all content. -#956890 by EclipseGc: Update views_content to not use views dependency since that is gone. -#954728 by EclipseGc: Update node template page function name to not collide with new hook_node_view(). -#946534 by EclipseGc: Add support for field content on all entitities. -#952586 by EclipseGc: Fix node_author content type. -#959206: If a context is not set when rendering content, attempt to guess the context (fixes Views panes where "From context" was added but pane was never edited.) -#961654 by benshell: drupal_alter() only supports 4 arguments. -#911362 by alex_b: Facilitate plugin cache resets for tests. -#945360 by naxoc: node_tag_new() not updated to D7. -#953804 by EclipseGc: Fix node comment rendering. -#953542 by EclipseGc: Fix node rendering. -#953776 by EclipseGc: Fix node link rendering. -#954772 by EclipseGc: Fix node build mode selection in node content type. -#954762 by EclipseGc: Fix comment forbidden theme call. -#954894 by EclipseGc: Fix breadcrumb content type. -#955180 by EclipseGc: Fix page primary navigation type. -#957190 by EclipseGc: Fix page secondary navigation type. -#957194 by EclipseGc: Remove mission content type, since D7 no longer has a site mission. -#957348 by EclipseGc: Fix search form URL path. -#952586 by andypost: Use format_username for displaying unlinked usernames. -#963800 by benshell: Fix query to fetch custom block title. -#983496 by Amitaibu: Fix term argument to use proper load function. -#989484 by Amitaibu: Fix notice in views plugin. -#982496: Fix token context. -#995026: Fix export UI during enable/disable which would throw notices and not properly set/unset menu items. -#998870 by Amitaibu: Fix notice when content has no icon by using function already designed for that. -#983576 by Amitaibu: Node view fallback task showed white screen. -#1004644 by pillarsdotnet: Update a missed theme() call to D7. -#1006162 by aspilicious: .info file cleanup. -#998312 by dereine: Support the expanded/hidden options that Views did for dependent.js -#955030: Remove no longer supported footer message content type. -Fix broken query in term context config. -#992022 by pcambra: Fix node autocomplete. -#946302 by BerdArt and arywyr: Fix PHP 5.3 reference error. -#980528 by das-peter: Notice fix with entity settings. -#999302 by troky: ctools_jump_menu() needed updating to new form parameters. -#964174: stylizer plugin theme delegation was in the wrong place, causing errors. -#991658 by burlap: Fully load the "user" context for the logged in user because not all fields are in $user. -#1014866 by das-peter: Smarter title panes, notice fix on access plugin descriptions. -#1015662 by troky: plugin .info files were not using correct filepaths. -#941780 by EclipseGc: Restore the "No blocks" functionality. -#951048 by EclipseGc: Tighter entity integration so that new entities are automatic contexts and relationships. -#941800 by me and aspilicious: Use Drupal 7 #machine_name automation on page manager pages and all export_ui defaults. -Disabled exportables and pages not properly greyed out. -#969208 by me and benshell: Get user_view and user profile working. -#941796: Recategorize blocks - -ctools 7.x-1.x-alpha1 -===================== - -Changelog reset for 7.x -Basic conversion done during sprint. diff --git a/sites/all/modules/contrib/dev/ctools/UPGRADE.txt b/sites/all/modules/contrib/dev/ctools/UPGRADE.txt index 844ecce..2fbfa4f 100644 --- a/sites/all/modules/contrib/dev/ctools/UPGRADE.txt +++ b/sites/all/modules/contrib/dev/ctools/UPGRADE.txt @@ -60,4 +60,4 @@ Upgrading from ctools-6.x-1.x to ctools-7.x-2.x: For all of these forms, the separate settings #trees in the form are now gone, so form ids may be adjusted. Also, these are now all real forms - using CTools form wizard instead of fake subforms as previously. \ No newline at end of file + using CTools form wizard instead of fake subforms as previously. diff --git a/sites/all/modules/contrib/dev/ctools/bulk_export/bulk_export.info b/sites/all/modules/contrib/dev/ctools/bulk_export/bulk_export.info index d27ea9a..9bb3394 100644 --- a/sites/all/modules/contrib/dev/ctools/bulk_export/bulk_export.info +++ b/sites/all/modules/contrib/dev/ctools/bulk_export/bulk_export.info @@ -4,9 +4,8 @@ core = 7.x dependencies[] = ctools package = Chaos tool suite -; Information added by drupal.org packaging script on 2013-04-03 -version = "7.x-1.3" +; Information added by Drupal.org packaging script on 2019-02-08 +version = "7.x-1.15" core = "7.x" project = "ctools" -datestamp = "1365013512" - +datestamp = "1549603691" diff --git a/sites/all/modules/contrib/dev/ctools/bulk_export/bulk_export.module b/sites/all/modules/contrib/dev/ctools/bulk_export/bulk_export.module index afb15b9..1050caa 100644 --- a/sites/all/modules/contrib/dev/ctools/bulk_export/bulk_export.module +++ b/sites/all/modules/contrib/dev/ctools/bulk_export/bulk_export.module @@ -137,7 +137,7 @@ function bulk_export_export($cli = FALSE, $options = array()) { // Add hook_ctools_plugin_api at the top of the module code, if there is any. if ($api_code) { foreach ($api_code as $api_hook => $text) { - $api = "\n/**\n"; + $api = "\n/**\n"; $api .= " * Implements hook_$api_hook().\n"; $api .= " */\n"; $api .= "function {$module_name}_$api_hook(\$module, \$api) {\n"; @@ -148,7 +148,7 @@ function bulk_export_export($cli = FALSE, $options = array()) { } if ($module_code) { - $module = " fieldset { + clear: none; +} + div.ctools-modal-content .resizable-textarea { width: auto; margin-left: 15em; diff --git a/sites/all/modules/contrib/dev/ctools/css/stylizer.css b/sites/all/modules/contrib/dev/ctools/css/stylizer.css index a16ec78..fc8dcb2 100644 --- a/sites/all/modules/contrib/dev/ctools/css/stylizer.css +++ b/sites/all/modules/contrib/dev/ctools/css/stylizer.css @@ -1,4 +1,3 @@ - /* Farbtastic placement */ .color-form { max-width: 50em; @@ -32,7 +31,8 @@ clear: left; /* LTR */ width: 14em; } -.color-form .form-text, .color-form .form-select { +.color-form .form-text, +.color-form .form-select { float: left; /* LTR */ } .color-form .form-text { @@ -84,7 +84,7 @@ cursor: default; } -.ctools-style-icons .form-item { +.ctools-style-icons .form-item { width: 100px; float: left; margin: 0 3px !important; diff --git a/sites/all/modules/contrib/dev/ctools/css/wizard.css b/sites/all/modules/contrib/dev/ctools/css/wizard.css index d42a2db..ab80def 100644 --- a/sites/all/modules/contrib/dev/ctools/css/wizard.css +++ b/sites/all/modules/contrib/dev/ctools/css/wizard.css @@ -1,4 +1,3 @@ - .wizard-trail { font-size: 120%; } diff --git a/sites/all/modules/contrib/dev/ctools/ctools.api.php b/sites/all/modules/contrib/dev/ctools/ctools.api.php index a7ab783..4481291 100644 --- a/sites/all/modules/contrib/dev/ctools/ctools.api.php +++ b/sites/all/modules/contrib/dev/ctools/ctools.api.php @@ -17,9 +17,9 @@ * Inform CTools about plugin types. * * @return array - * An array of plugin types, keyed by the type name. - * See the advanced help topic 'plugins-creating' for details of the array - * properties. + * An array of plugin types, keyed by the type name. + * See the advanced help topic 'plugins-creating' for details of the array + * properties. */ function hook_ctools_plugin_type() { $plugins['my_type'] = array( @@ -65,6 +65,7 @@ function hook_ctools_plugin_type() { * directory location is being requested. * @param string $plugin_type * The name of the plugin type for which a base directory is being requested. + * * @return string * The path where CTools' plugin system should search for plugin files, * relative to your module's root. Omit leading and trailing slashes. @@ -94,7 +95,7 @@ function hook_ctools_plugin_directory($owner, $plugin_type) { // Yes, this is exactly like Form 2 - just a different reasoning for it. return "plugins/$plugin_type"; } - // Finally, if nothing matches, it's safe to return nothing at all (or NULL). + // Finally, if nothing matches, it's safe to return nothing at all (== NULL). } /** @@ -155,7 +156,7 @@ function hook_ctools_api_hook_alter(&$list) { * allow to use it in the math expression api. * * @param $functions - * An array which has the functions as value. + * An array which has the functions as value. */ function hook_ctools_math_expression_functions_alter(&$functions) { // Allow to convert from degrees to radiant. @@ -199,7 +200,13 @@ function hook_ctools_render_alter(&$info, &$page, &$context) { * or categories or to rename content on specific sites. */ function hook_ctools_content_subtype_alter($subtype, $plugin) { - $subtype['render last'] = TRUE; + // Force a particular subtype of a particular plugin to render last. + if ($plugin['module'] === 'some_plugin_module' + && $plugin['name'] === 'some_plugin_name' + && $subtype['subtype_id'] === 'my_subtype_id' + ) { + $subtype['render last'] = TRUE; + } } /** @@ -225,6 +232,28 @@ function hook_ctools_entity_context_alter(&$plugin, &$entity, $plugin_id) { } } +/** + * Alter the conversion of context items by ctools context plugin convert()s. + * + * @param ctools_context $context + * The current context plugin object. If this implemented a 'convert' + * function, the value passed in has been processed by that function. + * @param string $converter + * A string associated with the plugin type, identifying the operation. + * @param string $value + * The value being converted; this is the only return from the function. + * @param $converter_options + * Array of key-value pairs to pass to a converter function from higher + * levels. + * + * @see ctools_context_convert_context() + */ +function hook_ctools_context_converter_alter($context, $converter, &$value, $converter_options) { + if ($converter === 'mystring') { + $value = 'fixed'; + } +} + /** * Alter the definition of entity context plugins. * @@ -263,6 +292,22 @@ function hook_ctools_cleanstring_CLEAN_ID_alter(&$settings) { $settings['lower case'] = TRUE; } +/** + * Let other modules modify the context handler before it is rendered. + * + * @param object $handler + * A handler for a current task and subtask. + * @param array $contexts + * An associative array of contexts. + * @param array $args + * An array for current args. + * + * @see ctools_context_handler_pre_render() + */ +function ctools_context_handler_pre_render($handler, $contexts, $args) { + $handler->conf['css_id'] = 'my-id'; +} + /** * @} End of "addtogroup hooks". */ diff --git a/sites/all/modules/contrib/dev/ctools/ctools.info b/sites/all/modules/contrib/dev/ctools/ctools.info index 8a7b0b6..188b55d 100644 --- a/sites/all/modules/contrib/dev/ctools/ctools.info +++ b/sites/all/modules/contrib/dev/ctools/ctools.info @@ -3,12 +3,24 @@ description = A library of helpful tools by Merlin of Chaos. core = 7.x package = Chaos tool suite files[] = includes/context.inc +files[] = includes/css-cache.inc files[] = includes/math-expr.inc files[] = includes/stylizer.inc -; Information added by drupal.org packaging script on 2013-04-03 -version = "7.x-1.3" +; Tests. +files[] = tests/context.test +files[] = tests/css.test +files[] = tests/css_cache.test +files[] = tests/ctools.plugins.test +files[] = tests/ctools.test +files[] = tests/math_expression.test +files[] = tests/math_expression_stack.test +files[] = tests/object_cache.test +files[] = tests/object_cache_unit.test +files[] = tests/page_tokens.test + +; Information added by Drupal.org packaging script on 2019-02-08 +version = "7.x-1.15" core = "7.x" project = "ctools" -datestamp = "1365013512" - +datestamp = "1549603691" diff --git a/sites/all/modules/contrib/dev/ctools/ctools.install b/sites/all/modules/contrib/dev/ctools/ctools.install index 1f961f1..d50bc9f 100644 --- a/sites/all/modules/contrib/dev/ctools/ctools.install +++ b/sites/all/modules/contrib/dev/ctools/ctools.install @@ -12,24 +12,25 @@ function ctools_requirements($phase) { $requirements = array(); if ($phase == 'runtime') { + $t = get_t(); $requirements['ctools_css_cache'] = array( - 'title' => t('CTools CSS Cache'), + 'title' => $t('CTools CSS Cache'), 'severity' => REQUIREMENT_OK, - 'value' => t('Exists'), + 'value' => $t('Exists'), ); $path = 'public://ctools/css'; if (!file_prepare_directory($path, FILE_CREATE_DIRECTORY)) { - $requirements['ctools_css_cache']['description'] = t('The CTools CSS cache directory, %path could not be created due to a misconfigured files directory. Please ensure that the files directory is correctly configured and that the webserver has permission to create directories.', array('%path' => file_uri_target($path))); + $requirements['ctools_css_cache']['description'] = $t('The CTools CSS cache directory, %path could not be created due to a misconfigured files directory. Please ensure that the files directory is correctly configured and that the webserver has permission to create directories.', array('%path' => file_uri_target($path))); $requirements['ctools_css_cache']['severity'] = REQUIREMENT_ERROR; - $requirements['ctools_css_cache']['value'] = t('Unable to create'); + $requirements['ctools_css_cache']['value'] = $t('Unable to create'); } if (!function_exists('error_get_last')) { - $requirements['ctools_php_52']['title'] = t('CTools PHP requirements'); - $requirements['ctools_php_52']['description'] = t('CTools requires certain features only available in PHP 5.2.0 or higher.'); + $requirements['ctools_php_52']['title'] = $t('CTools PHP requirements'); + $requirements['ctools_php_52']['description'] = $t('CTools requires certain features only available in PHP 5.2.0 or higher.'); $requirements['ctools_php_52']['severity'] = REQUIREMENT_WARNING; - $requirements['ctools_php_52']['value'] = t('PHP !version', array('!version' => phpversion())); + $requirements['ctools_php_52']['value'] = $t('PHP !version', array('!version' => phpversion())); } } @@ -37,10 +38,34 @@ function ctools_requirements($phase) { } /** - * Implements hook_schemea + * Implements hook_schema(). */ function ctools_schema() { - return ctools_schema_2(); + return ctools_schema_4(); +} + +/** + * Version 4 of the CTools schema. + */ +function ctools_schema_4() { + $schema = ctools_schema_3(); + + // Removed due to alternative database configuration issues. + // @see https://www.drupal.org/project/ctools/issues/2941920 + + return $schema; +} + +/** + * Version 3 of the CTools schema. + */ +function ctools_schema_3() { + $schema = ctools_schema_2(); + + // Update the 'obj' field to be 128 bytes long: + $schema['ctools_object_cache']['fields']['obj']['length'] = 128; + + return $schema; } /** @@ -49,7 +74,7 @@ function ctools_schema() { function ctools_schema_2() { $schema = ctools_schema_1(); - // update the 'name' field to be 128 bytes long: + // Update the 'name' field to be 128 bytes long: $schema['ctools_object_cache']['fields']['name']['length'] = 128; // Update the 'data' field to be type 'blob'. @@ -83,10 +108,10 @@ function ctools_schema_2() { 'serialize' => TRUE, ), 'filter' => array( - 'type' => 'int', - 'size' => 'tiny', - 'description' => 'Whether or not this CSS needs to be filtered.', - ), + 'type' => 'int', + 'size' => 'tiny', + 'description' => 'Whether or not this CSS needs to be filtered.', + ), ), 'primary key' => array('cid'), ); @@ -141,6 +166,21 @@ function ctools_schema_1() { return $schema; } +/** + * Implements hook_install(). + */ +function ctools_install() { + // Activate our custom cache handler for the CSS cache. + variable_set('cache_class_cache_ctools_css', 'CToolsCssCache'); +} + +/** + * Implements hook_uninstall(). + */ +function ctools_uninstall() { + variable_del('cache_class_cache_ctools_css'); +} + /** * Enlarge the ctools_object_cache.name column to prevent truncation and weird * errors. @@ -170,7 +210,7 @@ function ctools_update_6003() { if ($result) { db_delete('system')->condition('name', 'panels_views')->execute(); module_enable(array('views_content'), TRUE); - } + } } /** @@ -189,7 +229,7 @@ function ctools_update_6005() { } /** - * ctools_custom_content table was originally here, but is now moved to + * The ctools_custom_content table was originally here, but is now moved to * its own module. */ function ctools_update_6007() { @@ -203,17 +243,57 @@ function ctools_update_6007() { } /** - * ctools_object_cache needs to be defined as a blob. + * The ctools_object_cache needs to be defined as a blob. */ function ctools_update_6008() { db_delete('ctools_object_cache') ->execute(); db_change_field('ctools_object_cache', 'data', 'data', array( - 'type' => 'blob', - 'size' => 'big', - 'description' => 'Serialized data being stored.', - 'serialize' => TRUE, - ) + 'type' => 'blob', + 'size' => 'big', + 'description' => 'Serialized data being stored.', + 'serialize' => TRUE, + ) ); } + +/** + * Enable the custom CSS cache handler. + */ +function ctools_update_7000() { + variable_set('cache_class_cache_ctools_css', 'CToolsCssCache'); +} + +/** + * Increase the length of the ctools_object_cache.obj column. + */ +function ctools_update_7001() { + db_change_field('ctools_object_cache', 'obj', 'obj', array( + 'type' => 'varchar', + 'length' => '128', + 'not null' => TRUE, + 'description' => 'The type of the object this cache is attached to; this essentially represents the owner so that several sub-systems can use this cache.', + )); +} + +/** + * Increase the length of the ctools_object_cache.name column to 255. + */ +function ctools_update_7002() { + // Removed due to alternative database configuration issues. + // @see https://www.drupal.org/project/ctools/issues/2941920 +} + +/** + * Revert the length of the ctools_object_cache.name column back to 128. + */ +function ctools_update_7003() { + db_delete('ctools_object_cache')->execute(); + db_change_field('ctools_object_cache', 'name', 'name', array( + 'type' => 'varchar', + 'length' => '128', + 'not null' => TRUE, + 'description' => 'The name of the object this cache is attached to.', + )); +} diff --git a/sites/all/modules/contrib/dev/ctools/ctools.module b/sites/all/modules/contrib/dev/ctools/ctools.module index f014e74..62f2919 100644 --- a/sites/all/modules/contrib/dev/ctools/ctools.module +++ b/sites/all/modules/contrib/dev/ctools/ctools.module @@ -9,7 +9,24 @@ * must be implemented in the module file. */ -define('CTOOLS_API_VERSION', '2.0.7'); +define('CTOOLS_API_VERSION', '2.0.9'); + +/** + * The current working ctools version. + * + * In a release, it should be 7.x-1.x, which should match what drush make will + * create. In a dev format, it should be 7.x-1.(x+1)-dev, which will allow + * modules depending on new features in ctools to depend on ctools > 7.x-1.x. + * + * To define a specific version of CTools as a dependency for another module, + * simply include a dependency line in that module's info file, e.g.: + * ; Requires CTools v7.x-1.4 or newer. + * dependencies[] = ctools (>=1.4) + * + * @deprecated in CTools 1.15 and will be removed before CTools 2.0.0. + * Use the version provided by the drupal.org packaging system. + */ +define('CTOOLS_MODULE_VERSION', '7.x-1.13'); /** * Test the CTools API version. @@ -61,6 +78,9 @@ define('CTOOLS_API_VERSION', '2.0.7'); * The minimum version of CTools necessary for your software to run with it. * @param $maximum * The maximum version of CTools allowed for your software to run with it. + * + * @return bool + * TRUE if the running ctools is usable, FALSE otherwise. */ function ctools_api_version($minimum, $maximum = NULL) { if (version_compare(CTOOLS_API_VERSION, $minimum, '<')) { @@ -75,8 +95,7 @@ function ctools_api_version($minimum, $maximum = NULL) { } // ----------------------------------------------------------------------- -// General utility functions - +// General utility functions. /** * Include .inc files as necessary. * @@ -104,6 +123,7 @@ function ctools_api_version($minimum, $maximum = NULL) { * @param $dir * Optional subdirectory containing the include file. */ + function ctools_include($file, $module = 'ctools', $dir = 'includes') { static $used = array(); @@ -133,8 +153,8 @@ function ctools_form_include(&$form_state, $file, $module = 'ctools', $dir = 'in /** * Add an arbitrary path to the $form_state so it can work with form cache. * - * module_load_include uses an unfortunately annoying syntax to work, making it - * difficult to translate the more simple $path + $file syntax. + * The module_load_include() function uses an unfortunately annoying syntax to + * work, making it difficult to translate the more simple $path + $file syntax. */ function ctools_form_include_file(&$form_state, $filename) { if (!isset($form_state['build_info']['args'])) { @@ -158,6 +178,9 @@ function ctools_form_include_file(&$form_state, $filename) { * Optional module containing the include. * @param $dir * Optional subdirectory containing the include file. + * + * @return string + * A string containing the appropriate path from drupal root. */ function ctools_image_path($image, $module = 'ctools', $dir = 'images') { return drupal_get_path('module', $module) . "/$dir/" . $image; @@ -197,6 +220,9 @@ function ctools_add_css($file, $module = 'ctools', $dir = 'css') { * Optional module containing the include. * @param $dir * Optional subdirectory containing the include file. + * + * @return string + * A string containing the appropriate path from drupal root. */ function ctools_attach_css($file, $module = 'ctools', $dir = 'css') { return drupal_get_path('module', $module) . "/$dir/$file.css"; @@ -235,6 +261,9 @@ function ctools_add_js($file, $module = 'ctools', $dir = 'js') { * Optional module containing the include. * @param $dir * Optional subdirectory containing the include file. + * + * @return string + * A string containing the appropriate path from drupal root. */ function ctools_attach_js($file, $module = 'ctools', $dir = 'js') { return drupal_get_path('module', $module) . "/$dir/$file.js"; @@ -253,16 +282,29 @@ function ctools_get_roles() { return user_roles(); } -/* - * Break x,y,z and x+y+z into an array. Numeric only. +/** + * Parse integer sequences of the form "x,y,z" or "x+y+z" into separate values. + * + * A string with integers separated by comma (,) is reported as an 'and' set; + * separation by a plus sign (+) or a space ( ) is an 'or' set. The meaning + * of this is up to the caller. Negative or fractional numbers are not + * recognised. + * + * Additional space characters within or around the sequence are not allowed. * * @param $str * The string to parse. * - * @return $object - * An object containing - * - operator: Either 'and' or 'or' - * - value: An array of numeric values. + * @return object + * An object containing the properties: + * + * - operator: Either 'and' or 'or' when there are multiple matched values. + * Absent when invalid_input is TRUE or there is only one value. + * - value: An array of integers (never strings) from $str. An empty array is + * returned if the input is empty. A single integer input is returned + * as a single value, but no 'operator' is defined. + * - invalid_input: TRUE if input could not be parsed and the values array + * will contain just -1. This property is otherwise absent. */ function ctools_break_phrase($str) { $object = new stdClass(); @@ -272,7 +314,7 @@ function ctools_break_phrase($str) { $object->operator = 'or'; $object->value = preg_split('/[+ ]/', $str); } - else if (preg_match('/^([0-9]+,)*[0-9]+$/', $str)) { + elseif (preg_match('/^([0-9]+,)*[0-9]+$/', $str)) { $object->operator = 'and'; $object->value = explode(',', $str); } @@ -290,7 +332,7 @@ function ctools_break_phrase($str) { // Doubly ensure that all values are numeric only. foreach ($object->value as $id => $value) { - $object->value[$id] = intval($value); + $object->value[$id] = (int) $value; } return $object; @@ -298,25 +340,31 @@ function ctools_break_phrase($str) { /** * Set a token/value pair to be replaced later in the request, specifically in - * ctools_preprocess_page(). + * ctools_page_token_processing(). * - * @param $token + * @param string $token * The token to be replaced later, during page rendering. This should - * ideally be a string inside of an HTML comment, so that if there is - * no replacement, the token will not render on the page. - * @param $type + * ideally be a string inside of an HTML comment, so that if there is + * no replacement, the token will not render on the page. + * If $token is NULL, the token set is not changed, but is still + * returned. + * @param string $type * The type of the token. Can be either 'variable', which will pull data - * directly from the page variables - * @param $argument - * If $type == 'variable' then argument should be the key to fetch from - * the $variables. If $type == 'callback' then it should either be the - * callback, or an array that will be sent to call_user_func_array(). + * directly from the page variables, or 'callback', which causes a function + * to be called to calculate the value. No other values are supported. + * @param string|array $argument + * For $type of: + * - 'variable': argument should be the key to fetch from the $variables. + * - 'callback': then it should either be the callback function name as a + * string, or an array that will be sent to call_user_func_array(). Argument + * arrays must not use array keys (i.e. $a[0] is the first and $a[1] the + * second element, etc.) * - * @return + * @return array * A array of token/variable names to be replaced. */ function ctools_set_page_token($token = NULL, $type = NULL, $argument = NULL) { - static $tokens = array(); + $tokens = &drupal_static('ctools_set_page_token', array()); if (isset($token)) { $tokens[$token] = array($type, $argument); @@ -325,13 +373,32 @@ function ctools_set_page_token($token = NULL, $type = NULL, $argument = NULL) { } /** - * Easily set a token from the page variables. + * Reset the defined page tokens within this request. + * + * Introduced for simpletest purposes. Normally not needed. + */ +function ctools_reset_page_tokens() { + drupal_static_reset('ctools_set_page_token'); +} + +/** + * Set a replacement token from the containing element's children during #post_render. * * This function can be used like this: - * $token = ctools_set_variable_token('tabs'); + * $token = ctools_set_variable_token('tabs'); * - * $token will then be a simple replacement for the 'tabs' about of the - * variables available in the page template. + * The token "" would then be replaced by the value of + * this element's (sibling) render array key 'tabs' during post-render (or be + * deleted if there was no such key by that point). + * + * @param string $token + * The token string for the page callback, e.g. 'title'. + * + * @return string + * The constructed token. + * + * @see ctools_set_callback_token() + * @see ctools_page_token_processing() */ function ctools_set_variable_token($token) { $string = ''; @@ -340,10 +407,45 @@ function ctools_set_variable_token($token) { } /** - * Easily set a token from the page variables. + * Set a replacement token from the value of a function during #post_render. * * This function can be used like this: - * $token = ctools_set_variable_token('id', 'mymodule_myfunction'); + * $token = ctools_set_callback_token('id', 'mymodule_myfunction'); + * + * Or this (from its use in ctools_page_title_content_type_render): + * $token = ctools_set_callback_token('title', array( + * 'ctools_page_title_content_type_token', $conf['markup'], $conf['id'], $conf['class'] + * ) + * ); + * + * The token (e.g: "") + * would then be replaced during post-render by the return value of: + * + * ctools_page_title_content_type_token($value_markup, $value_id, $value_class); + * + * @param string $token + * The token string for the page callback, e.g. 'title'. + * + * @param string|array $callback + * For callback functions that require no args, the name of the function as a + * string; otherwise an array of two or more elements: the function name + * followed by one or more function arguments. + * + * NB: the value of $callback must be a procedural (non-class) function that + * passes the php function_exists() check. + * + * The callback function itself will be called with args dependent + * on $callback. If: + * - $callback is a string, the function is called with a reference to the + * render array; + * - $callback is an array, the function is called with $callback merged + * with an array containing a reference to the render array. + * + * @return string + * The constructed token. + * + * @see ctools_set_variable_token() + * @see ctools_page_token_processing() */ function ctools_set_callback_token($token, $callback) { // If the callback uses arguments they are considered in the token. @@ -367,6 +469,60 @@ function ctools_set_no_blocks($blocks = FALSE) { $status = $blocks; } +/** + * Wrapper function to create UUIDs via ctools, falls back on UUID module + * if it is enabled. This code is a copy of uuid.inc from the uuid module. + * + * @see http://php.net/uniqid#65879 + */ +function ctools_uuid_generate() { + if (!module_exists('uuid')) { + ctools_include('uuid'); + + $callback = drupal_static(__FUNCTION__); + + if (empty($callback)) { + if (function_exists('uuid_create') && !function_exists('uuid_make')) { + $callback = '_ctools_uuid_generate_pecl'; + } + elseif (function_exists('com_create_guid')) { + $callback = '_ctools_uuid_generate_com'; + } + else { + $callback = '_ctools_uuid_generate_php'; + } + } + return $callback(); + } + else { + return uuid_generate(); + } +} + +/** + * Check that a string appears to be in the format of a UUID. + * + * @see http://drupal.org/project/uuid + * + * @param $uuid + * The string to test. + * + * @return + * TRUE if the string is well formed. + */ +function ctools_uuid_is_valid($uuid = '') { + if (empty($uuid)) { + return FALSE; + } + if (function_exists('uuid_is_valid') || module_exists('uuid')) { + return uuid_is_valid($uuid); + } + else { + ctools_include('uuid'); + return uuid_is_valid($uuid); + } +} + /** * Add an array of classes to the body. * @@ -401,6 +557,8 @@ function ctools_class_add($classes, $hook = 'html') { */ function ctools_class_remove($classes, $hook = 'html') { if (!is_array($classes)) { + // @todo Consider using explode(' ', $classes); + // @todo Consider checking that $classes is a string before adding. $classes = array($classes); } @@ -413,12 +571,35 @@ function ctools_class_remove($classes, $hook = 'html') { } } -// ----------------------------------------------------------------------- -// Drupal core hooks +/** + * Reset the storage used for ctools_class_add and ctools_class_remove. + * + * @see ctools_class_add() + * @see ctools_class_remove() + */ +function ctools_class_reset() { + drupal_static_reset('ctools_process_classes'); +} +/** + * Return the classes for the body (added by ctools_class_add). + * + * @return array + * A copy of the array of classes to add to the body tag. If none have been + * added, this will be an empty array. + * + * @see ctools_class_add() + */ +function ctools_get_classes() { + return drupal_static('ctools_process_classes', array()); +} + +// ----------------------------------------------------------------------- +// Drupal core hooks. /** * Implement hook_init to keep our global CSS at the ready. */ + function ctools_init() { ctools_add_css('ctools'); // If we are sure that CTools' AJAX is in use, change the error handling. @@ -437,7 +618,7 @@ function ctools_init() { * Shutdown handler used during ajax operations to help catch fatal errors. */ function ctools_shutdown_handler() { - if (function_exists('error_get_last') AND ($error = error_get_last())) { + if (function_exists('error_get_last') && ($error = error_get_last())) { switch ($error['type']) { case E_ERROR: case E_CORE_ERROR: @@ -483,6 +664,19 @@ function ctools_menu() { return $items; } +/** + * Implements hook_permission(). + */ +function ctools_permission() { + return array( + 'use ctools import' => array( + 'title' => t('Use CTools importer'), + 'description' => t('The import functionality allows users to execute arbitrary PHP code, so extreme caution must be taken.'), + 'restrict access' => TRUE, + ), + ); +} + /** * Implementation of hook_cron. Clean up old caches. */ @@ -493,24 +687,16 @@ function ctools_cron() { } /** - * Ensure the CTools CSS cache is flushed whenever hook_flush_caches is invoked. + * Implements hook_flush_caches(). */ function ctools_flush_caches() { - // Do not actually flush caches if running on cron. Drupal uses this hook - // in an inconsistent fashion and it does not necessarily mean to *flush* - // caches when running from cron. Instead it's just getting a list of cache - // tables and may not do any flushing. - if (!empty($GLOBALS['locks']['cron'])) { - return; - } - - ctools_include('css'); - ctools_css_flush_caches(); + // Only return the CSS cache bin if it has been activated, to avoid + // drupal_flush_all_caches() from trying to truncate a non-existing table. + return variable_get('cache_class_cache_ctools_css', FALSE) ? array('cache_ctools_css') : array(); } /** * Implements hook_element_info_alter(). - * */ function ctools_element_info_alter(&$type) { ctools_include('dependent'); @@ -545,12 +731,33 @@ function ctools_registry_files_alter(&$files, $indexed_modules) { } // ----------------------------------------------------------------------- -// CTools hook implementations. +// FAPI hooks that must be in the .module file. +/** + * Alter the comment form to get a little more control over it. + */ +function ctools_form_comment_form_alter(&$form, &$form_state) { + if (!empty($form_state['ctools comment alter'])) { + // Force the form to post back to wherever we are. + $form['#action'] = url($_GET['q'], array('fragment' => 'comment-form')); + if (empty($form['#submit'])) { + $form['#submit'] = array('comment_form_submit'); + } + $form['#submit'][] = 'ctools_node_comment_form_submit'; + } +} + +function ctools_node_comment_form_submit(&$form, &$form_state) { + $form_state['redirect'][0] = $_GET['q']; +} + +// ----------------------------------------------------------------------- +// CTools hook implementations. /** * Implementation of hook_ctools_plugin_directory() to let the system know * where all our own plugins are. */ + function ctools_ctools_plugin_directory($owner, $plugin_type) { if ($owner == 'ctools') { return 'plugins/' . $plugin_type; @@ -571,11 +778,11 @@ function ctools_ctools_plugin_type() { // ----------------------------------------------------------------------- // Drupal theme preprocess hooks that must be in the .module file. - /** * A theme preprocess function to automatically allow panels-based node * templates based upon input when the panel was configured. */ + function ctools_preprocess_node(&$vars) { // The 'ctools_template_identifier' attribute of the node is added when the pane is // rendered. @@ -585,7 +792,25 @@ function ctools_preprocess_node(&$vars) { } } +/** + * Implements hook_page_alter(). + * + * Last ditch attempt to remove sidebar regions if the "no blocks" + * functionality has been activated. + * + * @see ctools_block_list_alter() + */ function ctools_page_alter(&$page) { + $check = drupal_static('ctools_set_no_blocks', TRUE); + if (!$check) { + foreach ($page as $region_id => $region) { + // @todo -- possibly we can set configuration for this so that users can + // specify which blocks will not get rendered. + if (strpos($region_id, 'sidebar') !== FALSE) { + unset($page[$region_id]); + } + } + } $page['#post_render'][] = 'ctools_page_token_processing'; } @@ -601,15 +826,16 @@ function ctools_page_token_processing($children, $elements) { list($type, $argument) = $key; switch ($type) { case 'variable': - $tokens[$token] = isset($variables[$argument]) ? $variables[$argument] : ''; + $tokens[$token] = isset($elements[$argument]) ? $elements[$argument] : ''; break; + case 'callback': if (is_string($argument) && function_exists($argument)) { - $tokens[$token] = $argument($variables); + $tokens[$token] = $argument($elements); } if (is_array($argument) && function_exists($argument[0])) { $function = array_shift($argument); - $argument = array_merge(array(&$variables), $argument); + $argument = array_merge(array(&$elements), $argument); $tokens[$token] = call_user_func_array($function, $argument); } break; @@ -631,7 +857,7 @@ function ctools_process(&$variables, $hook) { return; } - $classes = drupal_static('ctools_process_classes', array()); + $classes = ctools_get_classes(); // Process the classses to add. if (!empty($classes[$hook]['add'])) { @@ -652,7 +878,6 @@ function ctools_process(&$variables, $hook) { // ----------------------------------------------------------------------- // Menu callbacks that must be in the .module file. - /** * Determine if the current user has access via a plugin. * @@ -676,6 +901,7 @@ function ctools_process(&$variables, $hook) { * @return * TRUE if access is granted, false if otherwise. */ + function ctools_access_menu($access) { // Short circuit everything if there are no access tests. if (empty($access['plugins'])) { @@ -704,7 +930,7 @@ function ctools_access_menu($access) { * An indexed array of zero or more permission strings to be checked by * user_access(). * - * @return + * @return bool * Iff all checks pass will this function return TRUE. If an invalid argument * is passed (e.g., not a string), this function errs on the safe said and * returns FALSE. @@ -733,6 +959,15 @@ function ctools_js_load($js) { return 0; } +/** + * Provides the default value for %ctools_js. + * + * This allows drupal_valid_path() to work with %ctools_js. + */ +function ctools_js_to_arg($arg) { + return empty($arg) || $arg == '%' ? 'nojs' : $arg; +} + /** * Menu _load hook. * @@ -757,7 +992,6 @@ function ctools_export_ui_load($item_name, $plugin_name) { // ----------------------------------------------------------------------- // Caching callbacks on behalf of export-ui. - /** * Menu access callback for various tasks of export-ui. */ @@ -801,7 +1035,7 @@ function ctools_export_ui_ctools_access_get($argument) { } /** - * Callback for access control ajax form on behalf of export ui + * Callback for access control ajax form on behalf of export ui. * * Returns the cached access config and contexts used. * Note that this is assuming that access will be in $item->access -- if it @@ -834,8 +1068,9 @@ function ctools_menu_local_tasks_alter(&$data, $router_item, $root_path) { } /** - * Implement hook_block_list_alter() to potentially remove blocks. + * Implements hook_block_list_alter(). * + * Used to potentially remove blocks. * This exists in order to replicate Drupal 6's "no blocks" functionality. */ function ctools_block_list_alter(&$blocks) { @@ -852,11 +1087,25 @@ function ctools_block_list_alter(&$blocks) { } /** - * Implement hook_modules_enabled to clear static caches for detecting new plugins + * Implements hook_modules_enabled(). + * + * Clear caches for detecting new plugins. */ function ctools_modules_enabled($modules) { ctools_include('plugins'); ctools_get_plugins_reset(); + cache_clear_all('ctools_plugin_files:', 'cache', TRUE); +} + +/** + * Implements hook_modules_disabled(). + * + * Clear caches for removing disabled plugins. + */ +function ctools_modules_disabled($modules) { + ctools_include('plugins'); + ctools_get_plugins_reset(); + cache_clear_all('ctools_plugin_files:', 'cache', TRUE); } /** @@ -884,6 +1133,7 @@ function ctools_ctools_entity_context_alter(&$plugin, &$entity, $plugin_id) { case 'entity_id:taxonomy_term': $plugin['no ui'] = TRUE; break; + case 'entity:user': $plugin = ctools_get_context('user'); unset($plugin['no ui']); @@ -903,3 +1153,53 @@ function ctools_ctools_entity_context_alter(&$plugin, &$entity, $plugin_id) { } } } + +/** + * Implements hook_field_create_field(). + */ +function ctools_field_create_field($field) { + ctools_flush_field_caches(); +} + +/** + * Implements hook_field_create_instance(). + */ +function ctools_field_create_instance($instance) { + ctools_flush_field_caches(); +} + +/** + * Implements hook_field_delete_field(). + */ +function ctools_field_delete_field($field) { + ctools_flush_field_caches(); +} + +/** + * Implements hook_field_delete_instance(). + */ +function ctools_field_delete_instance($instance) { + ctools_flush_field_caches(); +} + +/** + * Implements hook_field_update_field(). + */ +function ctools_field_update_field($field, $prior_field, $has_data) { + ctools_flush_field_caches(); +} + +/** + * Implements hook_field_update_instance(). + */ +function ctools_field_update_instance($instance, $prior_instance) { + ctools_flush_field_caches(); +} + +/** + * Clear field related caches. + */ +function ctools_flush_field_caches() { + // Clear caches of 'Entity field' content type plugin. + cache_clear_all('ctools_entity_field_content_type_content_types', 'cache'); +} diff --git a/sites/all/modules/contrib/dev/ctools/ctools_access_ruleset/ctools_access_ruleset.info b/sites/all/modules/contrib/dev/ctools/ctools_access_ruleset/ctools_access_ruleset.info index 1019253..3fc3556 100644 --- a/sites/all/modules/contrib/dev/ctools/ctools_access_ruleset/ctools_access_ruleset.info +++ b/sites/all/modules/contrib/dev/ctools/ctools_access_ruleset/ctools_access_ruleset.info @@ -4,9 +4,8 @@ core = 7.x package = Chaos tool suite dependencies[] = ctools -; Information added by drupal.org packaging script on 2013-04-03 -version = "7.x-1.3" +; Information added by Drupal.org packaging script on 2019-02-08 +version = "7.x-1.15" core = "7.x" project = "ctools" -datestamp = "1365013512" - +datestamp = "1549603691" diff --git a/sites/all/modules/contrib/dev/ctools/ctools_access_ruleset/ctools_access_ruleset.install b/sites/all/modules/contrib/dev/ctools/ctools_access_ruleset/ctools_access_ruleset.install index 3f00877..70afb3c 100644 --- a/sites/all/modules/contrib/dev/ctools/ctools_access_ruleset/ctools_access_ruleset.install +++ b/sites/all/modules/contrib/dev/ctools/ctools_access_ruleset/ctools_access_ruleset.install @@ -1,5 +1,9 @@ t('Custom rulesets are combinations of access plugins you can use for access control, selection criteria and pane visibility.'), ); - // Load all mini panels and their displays. + // Load all mini panels and their displays. ctools_include('export'); $items = ctools_export_crud_load_all('ctools_access_ruleset'); $count = 0; diff --git a/sites/all/modules/contrib/dev/ctools/ctools_access_ruleset/plugins/access/ruleset.inc b/sites/all/modules/contrib/dev/ctools/ctools_access_ruleset/plugins/access/ruleset.inc index f8abea6..95f32c3 100644 --- a/sites/all/modules/contrib/dev/ctools/ctools_access_ruleset/plugins/access/ruleset.inc +++ b/sites/all/modules/contrib/dev/ctools/ctools_access_ruleset/plugins/access/ruleset.inc @@ -35,7 +35,7 @@ function ctools_ruleset_ctools_access_merge_plugin($plugin, $parent, $item) { $plugin['required context'] = array(); foreach ($item->requiredcontexts as $context) { $info = ctools_get_context($context['name']); - // TODO: allow an optional setting + // TODO: allow an optional setting. $plugin['required context'][] = new ctools_context_required($context['identifier'], $info['context name']); } } @@ -72,7 +72,7 @@ function ctools_ruleset_ctools_access_get_children($plugin, $parent) { } /** - * Settings form for the 'by ruleset' access plugin + * Settings form for the 'by ruleset' access plugin. */ function ctools_ruleset_ctools_access_settings(&$form, &$form_state, $conf) { if (!empty($form_state['plugin']['ruleset']->admin_description)) { @@ -106,4 +106,3 @@ function ctools_ruleset_ctools_access_summary($conf, $context, $plugin) { return check_plain($plugin['ruleset']->admin_title); } } - diff --git a/sites/all/modules/contrib/dev/ctools/ctools_access_ruleset/plugins/export_ui/ctools_access_ruleset.inc b/sites/all/modules/contrib/dev/ctools/ctools_access_ruleset/plugins/export_ui/ctools_access_ruleset.inc index d2a1c60..2589ac3 100644 --- a/sites/all/modules/contrib/dev/ctools/ctools_access_ruleset/plugins/export_ui/ctools_access_ruleset.inc +++ b/sites/all/modules/contrib/dev/ctools/ctools_access_ruleset/plugins/export_ui/ctools_access_ruleset.inc @@ -1,5 +1,9 @@ 'ctools_access_ruleset', 'access' => 'administer ctools access ruleset', @@ -26,4 +30,3 @@ $plugin = array( ), ), ); - diff --git a/sites/all/modules/contrib/dev/ctools/ctools_access_ruleset/plugins/export_ui/ctools_access_ruleset_ui.class.php b/sites/all/modules/contrib/dev/ctools/ctools_access_ruleset/plugins/export_ui/ctools_access_ruleset_ui.class.php index b181464..c9f8c20 100644 --- a/sites/all/modules/contrib/dev/ctools/ctools_access_ruleset/plugins/export_ui/ctools_access_ruleset_ui.class.php +++ b/sites/all/modules/contrib/dev/ctools/ctools_access_ruleset/plugins/export_ui/ctools_access_ruleset_ui.class.php @@ -2,7 +2,7 @@ class ctools_access_ruleset_ui extends ctools_export_ui { - function edit_form_context(&$form, &$form_state) { + public function edit_form_context(&$form, &$form_state) { ctools_include('context-admin'); ctools_context_admin_includes(); ctools_add_css('ruleset'); @@ -27,7 +27,7 @@ class ctools_access_ruleset_ui extends ctools_export_ui { ctools_context_add_relationship_form($module, $form, $form_state, $form['right']['relationships_table'], $form_state['item'], $name); } - function edit_form_rules(&$form, &$form_state) { + public function edit_form_rules(&$form, &$form_state) { // The 'access' UI passes everything via $form_state, unlike the 'context' UI. // The main difference is that one is about 3 years newer than the other. ctools_include('context'); @@ -43,11 +43,12 @@ class ctools_access_ruleset_ui extends ctools_export_ui { $form = ctools_access_admin_form($form, $form_state); } - function edit_form_rules_submit(&$form, &$form_state) { + public function edit_form_rules_submit(&$form, &$form_state) { $form_state['item']->access['logic'] = $form_state['values']['logic']; } - function edit_form_submit(&$form, &$form_state) { + public function edit_form_submit(&$form, &$form_state) { parent::edit_form_submit($form, $form_state); } + } diff --git a/sites/all/modules/contrib/dev/ctools/ctools_ajax_sample/css/ctools-ajax-sample.css b/sites/all/modules/contrib/dev/ctools/ctools_ajax_sample/css/ctools-ajax-sample.css index 8df17de..c312e99 100644 --- a/sites/all/modules/contrib/dev/ctools/ctools_ajax_sample/css/ctools-ajax-sample.css +++ b/sites/all/modules/contrib/dev/ctools/ctools_ajax_sample/css/ctools-ajax-sample.css @@ -1,52 +1,52 @@ div.ctools-sample-modal-content { - background:none; - border:0; - color:#000000; - margin:0; - padding:0; - text-align:left; + background: none; + border: 0; + color: #000; + margin: 0; + padding: 0; + text-align: left; } -div.ctools-sample-modal-content .modal-scroll{ - overflow:hidden; - overflow-y:auto; +div.ctools-sample-modal-content .modal-scroll { + overflow: hidden; + overflow-y: auto; } div.ctools-sample-modal-content #popups-overlay { - background-color:transparent; + background-color: transparent; } div.ctools-sample-modal-content #popups-loading { - width:248px; - position:absolute; - display:none; - opacity:1; + width: 248px; + position: absolute; + display: none; + opacity: 1; -moz-border-radius: 8px; -webkit-border-radius: 8px; - z-index:99; + z-index: 99; } div.ctools-sample-modal-content #popups-loading span.popups-loading-message { - background:#FFF url(../images/loading-large.gif) no-repeat 8px center; - display:block; - color:#444444; - font-family:Arial; - font-size:22px; - font-weight:bold; - height:36px; - line-height:36px; - padding:0 40px; + background: #fff url(../images/loading-large.gif) no-repeat 8px center; + display: block; + color: #444; + font-family: Arial, serif; + font-size: 22px; + font-weight: bold; + height: 36px; + line-height: 36px; + padding: 0 40px; } div.ctools-sample-modal-content #popups-loading table, div.ctools-sample-modal-content .popups-box table { - margin:0px; + margin: 0; } div.ctools-sample-modal-content #popups-loading tbody, div.ctools-sample-modal-content .popups-box tbody { - border:none; + border: none; } div.ctools-sample-modal-content .popups-box tr { - background-color:transparent; + background-color: transparent; } div.ctools-sample-modal-content td.popups-border { background: url(../images/popups-border.png); - background-color:transparent; + background-color: transparent; border: none; } div.ctools-sample-modal-content td.popups-tl, @@ -54,79 +54,94 @@ div.ctools-sample-modal-content td.popups-tr, div.ctools-sample-modal-content td.popups-bl, div.ctools-sample-modal-content td.popups-br { background-repeat: no-repeat; - height:10px; - padding:0px; + height: 10px; + padding: 0; +} +div.ctools-sample-modal-content td.popups-tl { + background-position: 0 0; } -div.ctools-sample-modal-content td.popups-tl { background-position: 0px 0px; } div.ctools-sample-modal-content td.popups-t, div.ctools-sample-modal-content td.popups-b { - background-position: 0px -40px; + background-position: 0 -40px; background-repeat: repeat-x; } -div.ctools-sample-modal-content td.popups-tr { background-position: 0px -10px; width: 10px; } +div.ctools-sample-modal-content td.popups-tr { + background-position: 0 -10px; + width: 10px; +} div.ctools-sample-modal-content td.popups-cl, div.ctools-sample-modal-content td.popups-cr { background-position: -10px 0; background-repeat: repeat-y; - width:10px; + width: 10px; } div.ctools-sample-modal-content td.popups-cl, div.ctools-sample-modal-content td.popups-cr, -div.ctools-sample-modal-content td.popups-c { padding:0; border: none; } -div.ctools-sample-modal-content td.popups-c { background:#fff; } -div.ctools-sample-modal-content td.popups-bl { background-position: 0px -20px; } -div.ctools-sample-modal-content td.popups-br { background-position: 0px -30px; width: 10px; } +div.ctools-sample-modal-content td.popups-c { + padding: 0; + border: none; +} +div.ctools-sample-modal-content td.popups-c { + background: #fff; +} +div.ctools-sample-modal-content td.popups-bl { + background-position: 0 -20px; +} +div.ctools-sample-modal-content td.popups-br { + background-position: 0 -30px; + width: 10px; +} div.ctools-sample-modal-content .popups-box, div.ctools-sample-modal-content #popups-loading { - border: 0px solid #454545; - opacity:1; - overflow:hidden; - padding:0; - background-color:transparent; + border: 0 solid #454545; + opacity: 1; + overflow: hidden; + padding: 0; + background-color: transparent; } div.ctools-sample-modal-content .popups-container { - overflow:hidden; - height:100%; - background-color:#fff; + overflow: hidden; + height: 100%; + background-color: #fff; } div.ctools-sample-modal-content div.popups-title { - -moz-border-radius-topleft: 0px; - -webkit-border-radius-topleft: 0px; - margin-bottom:0px; - background-color:#ff7200; - border:1px solid #ce5c00; - padding:4px 10px 5px; - color:white; - font-size:1em; - font-weight:bold; + -moz-border-radius-topleft: 0; + -webkit-border-radius-topleft: 0; + margin-bottom: 0; + background-color: #ff7200; + border: 1px solid #ce5c00; + padding: 4px 10px 5px; + color: white; + font-size: 1em; + font-weight: bold; } div.ctools-sample-modal-content .popups-body { - background-color:#fff; - padding:8px; + background-color: #fff; + padding: 8px; } div.ctools-sample-modal-content .popups-box .popups-buttons, div.ctools-sample-modal-content .popups-box .popups-footer { - background-color:#fff; + background-color: #fff; } div.ctools-sample-modal-content .popups-title a.close { color: #fff; - text-decoration:none; + text-decoration: none; } div.ctools-sample-modal-content .popups-close { - font-size:120%; - float:right; - text-align:right; + font-size: 120%; + float: right; + text-align: right; } div.ctools-sample-modal-content .modal-loading-wrapper { - width:220px; - height:19px; - margin:0 auto; - margin-top:2%; + width: 220px; + height: 19px; + margin: 0 auto; + margin-top: 2%; } -div.ctools-sample-modal-content tbody{ - border:none; +div.ctools-sample-modal-content tbody { + border: none; } div.ctools-sample-modal-content .modal-content .modal-throbber-wrapper img { diff --git a/sites/all/modules/contrib/dev/ctools/ctools_ajax_sample/ctools_ajax_sample.info b/sites/all/modules/contrib/dev/ctools/ctools_ajax_sample/ctools_ajax_sample.info index 03ccb34..c2a52df 100644 --- a/sites/all/modules/contrib/dev/ctools/ctools_ajax_sample/ctools_ajax_sample.info +++ b/sites/all/modules/contrib/dev/ctools/ctools_ajax_sample/ctools_ajax_sample.info @@ -4,9 +4,8 @@ package = Chaos tool suite dependencies[] = ctools core = 7.x -; Information added by drupal.org packaging script on 2013-04-03 -version = "7.x-1.3" +; Information added by Drupal.org packaging script on 2019-02-08 +version = "7.x-1.15" core = "7.x" project = "ctools" -datestamp = "1365013512" - +datestamp = "1549603691" diff --git a/sites/all/modules/contrib/dev/ctools/ctools_ajax_sample/ctools_ajax_sample.install b/sites/all/modules/contrib/dev/ctools/ctools_ajax_sample/ctools_ajax_sample.install index 04325db..e0fdfc6 100644 --- a/sites/all/modules/contrib/dev/ctools/ctools_ajax_sample/ctools_ajax_sample.install +++ b/sites/all/modules/contrib/dev/ctools/ctools_ajax_sample/ctools_ajax_sample.install @@ -1,18 +1,18 @@ 'Chaos Tools AJAX Demo', - 'page callback' => 'ctools_ajax_sample_page', - 'access callback' => TRUE, - 'type' => MENU_NORMAL_ITEM, + 'title' => 'Chaos Tools AJAX Demo', + 'page callback' => 'ctools_ajax_sample_page', + 'access callback' => TRUE, + 'type' => MENU_NORMAL_ITEM, ); $items['ctools_ajax_sample/simple_form'] = array( 'title' => 'Simple Form', @@ -26,39 +26,39 @@ function ctools_ajax_sample_menu() { 'type' => MENU_CALLBACK, ); $items['ctools_ajax_sample/%ctools_js/hello'] = array( - 'title' => 'Hello World', - 'page callback' => 'ctools_ajax_sample_hello', - 'page arguments' => array(1), - 'access callback' => TRUE, - 'type' => MENU_CALLBACK, + 'title' => 'Hello World', + 'page callback' => 'ctools_ajax_sample_hello', + 'page arguments' => array(1), + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, ); $items['ctools_ajax_sample/%ctools_js/tablenix/%'] = array( - 'title' => 'Hello World', - 'page callback' => 'ctools_ajax_sample_tablenix', - 'page arguments' => array(1, 3), - 'access callback' => TRUE, - 'type' => MENU_CALLBACK, + 'title' => 'Hello World', + 'page callback' => 'ctools_ajax_sample_tablenix', + 'page arguments' => array(1, 3), + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, ); $items['ctools_ajax_sample/%ctools_js/login'] = array( - 'title' => 'Login', - 'page callback' => 'ctools_ajax_sample_login', - 'page arguments' => array(1), - 'access callback' => TRUE, - 'type' => MENU_CALLBACK, + 'title' => 'Login', + 'page callback' => 'ctools_ajax_sample_login', + 'page arguments' => array(1), + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, ); $items['ctools_ajax_sample/%ctools_js/animal'] = array( - 'title' => 'Animal', - 'page callback' => 'ctools_ajax_sample_animal', - 'page arguments' => array(1), - 'access callback' => TRUE, - 'type' => MENU_CALLBACK, + 'title' => 'Animal', + 'page callback' => 'ctools_ajax_sample_animal', + 'page arguments' => array(1), + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, ); $items['ctools_ajax_sample/%ctools_js/login/%'] = array( - 'title' => 'Post-Login Action', - 'page callback' => 'ctools_ajax_sample_login_success', - 'page arguments' => array(1, 3), - 'access callback' => TRUE, - 'type' => MENU_CALLBACK, + 'title' => 'Post-Login Action', + 'page callback' => 'ctools_ajax_sample_login_success', + 'page arguments' => array(1, 3), + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, ); $items['ctools_ajax_sample/jumped'] = array( 'title' => 'Successful Jumping', @@ -104,11 +104,11 @@ function ctools_ajax_sample_theme() { } // --------------------------------------------------------------------------- -// Page callbacks - +// Page callbacks. /** * Page callback to display links and render a container for AJAX stuff. */ + function ctools_ajax_sample_page() { global $user; @@ -156,14 +156,14 @@ function ctools_ajax_sample_page() { // The extra class points to the info in ctools-sample-style which we added // to the settings, prefixed with 'ctools-modal'. - $links[] = ctools_modal_text_button(t('Modal Login (custom style)'), 'ctools_ajax_sample/nojs/login', t('Login via modal'), 'ctools-modal-ctools-sample-style'); + $links[] = ctools_modal_text_button(t('Modal Login (custom style)'), 'ctools_ajax_sample/nojs/login', t('Login via modal'), 'ctools-modal-ctools-sample-style'); } // Four ways to do our animal picking wizard. $button_form = ctools_ajax_sample_ajax_button_form(); $links[] = l(t('Wizard (no modal)'), 'ctools_ajax_sample/nojs/animal'); $links[] = ctools_modal_text_button(t('Wizard (default modal)'), 'ctools_ajax_sample/nojs/animal', t('Pick an animal')); - $links[] = ctools_modal_text_button(t('Wizard (custom modal)'), 'ctools_ajax_sample/nojs/animal', t('Pick an animal'), 'ctools-modal-ctools-sample-style'); + $links[] = ctools_modal_text_button(t('Wizard (custom modal)'), 'ctools_ajax_sample/nojs/animal', t('Pick an animal'), 'ctools-modal-ctools-sample-style'); $links[] = drupal_render($button_form); $links[] = ctools_ajax_text_button(t('Hello world!'), "ctools_ajax_sample/nojs/hello", t('Replace text with "hello world"')); @@ -176,9 +176,9 @@ function ctools_ajax_sample_page() { // Create a table that we can have data removed from via AJAX. $header = array(t('Row'), t('Content'), t('Actions')); $rows = array(); - for($i = 1; $i < 11; $i++) { + for ($i = 1; $i < 11; $i++) { $rows[] = array( - 'class' => array('ajax-sample-row-'. $i), + 'class' => array('ajax-sample-row-' . $i), 'data' => array( $i, md5($i), @@ -189,10 +189,10 @@ function ctools_ajax_sample_page() { $output .= theme('table', array('header' => $header, 'rows' => $rows, array('class' => array('ajax-sample-table')))); - // Show examples of ctools javascript widgets - $output .= '

    '. t('CTools Javascript Widgets') .'

    '; + // Show examples of ctools javascript widgets. + $output .= '

    ' . t('CTools Javascript Widgets') . '

    '; - // Create a drop down menu + // Create a drop down menu. $links = array(); $links[] = array('title' => t('Link 1'), 'href' => $_GET['q']); $links[] = array('title' => t('Link 2'), 'href' => $_GET['q']); @@ -201,16 +201,16 @@ function ctools_ajax_sample_page() { $output .= '

    ' . t('Drop Down Menu') . '

    '; $output .= theme('ctools_dropdown', array('title' => t('Click to Drop Down'), 'links' => $links)); - // Create a collapsible div + // Create a collapsible div. $handle = t('Click to Collapse'); $content = 'Nulla ligula ante, aliquam at adipiscing egestas, varius vel arcu. Etiam laoreet elementum mi vel consequat. Etiam scelerisque lorem vel neque consequat quis bibendum libero congue. Nulla facilisi. Mauris a elit a leo feugiat porta. Phasellus placerat cursus est vitae elementum.'; - $output .= '

    '. t('Collapsible Div') .'

    '; + $output .= '

    ' . t('Collapsible Div') . '

    '; $output .= theme('ctools_collapsible', array('handle' => $handle, 'content' => $content, 'collapsed' => FALSE)); - // Create a jump menu + // Create a jump menu. ctools_include('jump-menu'); $form = drupal_get_form('ctools_ajax_sample_jump_menu_form'); - $output .= '

    '. t('Jump Menu') .'

    '; + $output .= '

    ' . t('Jump Menu') . '

    '; $output .= drupal_render($form); return array('markup' => array('#markup' => $output)); @@ -225,7 +225,8 @@ function ctools_ajax_sample_hello($js = NULL) { ctools_include('ajax'); $commands = array(); $commands[] = ajax_command_html('#ctools-sample', $output); - print ajax_render($commands); // this function exits. + // This function exits. + print ajax_render($commands); exit; } else { @@ -234,7 +235,7 @@ function ctools_ajax_sample_hello($js = NULL) { } /** - * Nix a row from a table and restripe. + * Nix a row from a table and restripe. */ function ctools_ajax_sample_tablenix($js, $row) { if (!$js) { @@ -272,7 +273,7 @@ function ctools_ajax_sample_login($js = NULL) { $output = array(); $inplace = ctools_ajax_text_button(t('remain here'), 'ctools_ajax_sample/nojs/login/inplace', t('Go to your account')); $account = ctools_ajax_text_button(t('your account'), 'ctools_ajax_sample/nojs/login/user', t('Go to your account')); - $output[] = ctools_modal_command_display(t('Login Success'), ''); + $output[] = ctools_modal_command_display(t('Login Success'), ''); } print ajax_render($output); exit; @@ -283,7 +284,7 @@ function ctools_ajax_sample_login($js = NULL) { */ function ctools_ajax_sample_login_success($js, $action) { if (!$js) { - // we should never be here out of ajax context + // We should never be here out of ajax context. return MENU_NOT_FOUND; } @@ -291,11 +292,11 @@ function ctools_ajax_sample_login_success($js, $action) { ctools_add_js('ajax-responder'); $commands = array(); if ($action == 'inplace') { - // stay here + // Stay here. $commands[] = ctools_ajax_command_reload(); } else { - // bounce bounce + // Bounce bounce. $commands[] = ctools_ajax_command_redirect('user'); } print ajax_render($commands); @@ -318,18 +319,18 @@ function ctools_ajax_sample_animal($js = NULL, $step = NULL) { 'show back' => TRUE, 'show cancel' => TRUE, 'show return' => FALSE, - 'next callback' => 'ctools_ajax_sample_wizard_next', + 'next callback' => 'ctools_ajax_sample_wizard_next', 'finish callback' => 'ctools_ajax_sample_wizard_finish', 'cancel callback' => 'ctools_ajax_sample_wizard_cancel', - // this controls order, as well as form labels + // This controls order, as well as form labels. 'order' => array( 'start' => t('Choose animal'), ), - // here we map a step to a form id. + // Here we map a step to a form id. 'forms' => array( - // e.g. this for the step at wombat/create + // e.g. this for the step at wombat/create. 'start' => array( - 'form id' => 'ctools_ajax_sample_start' + 'form id' => 'ctools_ajax_sample_start', ), ), ); @@ -341,7 +342,6 @@ function ctools_ajax_sample_animal($js = NULL, $step = NULL) { // in creation. // // We skip all this here by just using an id of 1. - $object_id = 1; if (empty($step)) { @@ -406,7 +406,7 @@ function ctools_ajax_sample_animal($js = NULL, $step = NULL) { $commands[] = ajax_command_html('#ctools-sample', $animal); $commands[] = ctools_modal_command_dismiss(); } - else if (!empty($form_state['cancel'])) { + elseif (!empty($form_state['cancel'])) { // If cancelling, return to the activity. $commands[] = ctools_modal_command_dismiss(); } @@ -420,7 +420,7 @@ function ctools_ajax_sample_animal($js = NULL, $step = NULL) { if ($output === FALSE || !empty($form_state['complete'])) { return $animal; } - else if (!empty($form_state['cancel'])) { + elseif (!empty($form_state['cancel'])) { drupal_goto('ctools_ajax_sample'); } else { @@ -430,11 +430,11 @@ function ctools_ajax_sample_animal($js = NULL, $step = NULL) { } // --------------------------------------------------------------------------- -// Themes - +// Themes. /** * Theme function for main rendered output. */ + function theme_ctools_ajax_sample_container($vars) { $output = '
    '; $output .= $vars['content']; @@ -445,7 +445,6 @@ function theme_ctools_ajax_sample_container($vars) { // --------------------------------------------------------------------------- // Stuff needed for our little wizard. - /** * Get a list of our animals and associated forms. * @@ -453,6 +452,7 @@ function theme_ctools_ajax_sample_container($vars) { * which is often how it will work in the real world. If using CTools, what * you would probably really have, here, is a set of plugins for each animal. */ + function ctools_ajax_sample_animals() { return array( 'sheep' => array( @@ -478,10 +478,10 @@ function ctools_ajax_sample_animals() { // --------------------------------------------------------------------------- // Wizard caching helpers. - /** * Store our little cache so that we can retain data from form to form. */ + function ctools_ajax_sample_cache_set($id, $object) { ctools_include('object-cache'); ctools_object_cache_set('ctools_ajax_sample', $id, $object); @@ -495,7 +495,7 @@ function ctools_ajax_sample_cache_get($id) { $object = ctools_object_cache_get('ctools_ajax_sample', $id); if (!$object) { // Create a default object. - $object = new stdClass; + $object = new stdClass(); $object->type = 'unknown'; $object->name = ''; } @@ -513,18 +513,18 @@ function ctools_ajax_sample_cache_clear($id) { // --------------------------------------------------------------------------- // Wizard in-between helpers; what to do between or after forms. - /** * Handle the 'next' click on the add/edit pane form wizard. * * All we need to do is store the updated pane in the cache. */ + function ctools_ajax_sample_wizard_next(&$form_state) { ctools_ajax_sample_cache_set($form_state['object_id'], $form_state['object']); } /** - * Handle the 'finish' click on teh add/edit pane form wizard. + * Handle the 'finish' click on the add/edit pane form wizard. * * All we need to do is set a flag so the return can handle adding * the pane. @@ -542,10 +542,10 @@ function ctools_ajax_sample_wizard_cancel(&$form_state) { // --------------------------------------------------------------------------- // Wizard forms for our simple info collection wizard. - /** * Wizard start form. Choose an animal. */ + function ctools_ajax_sample_start($form, &$form_state) { $form_state['title'] = t('Choose animal'); @@ -713,7 +713,7 @@ function ctools_ajax_sample_show_raptor($object) { } /** - * Helper function to provide a sample jump menu form + * Helper function to provide a sample jump menu form. */ function ctools_ajax_sample_jump_menu_form() { $url = url('ctools_ajax_sample/jumped'); @@ -723,7 +723,7 @@ function ctools_ajax_sample_jump_menu_form() { } /** - * Provide a message to the user that the jump menu worked + * Provide a message to the user that the jump menu worked. */ function ctools_ajax_sample_jump_menu_page() { $return_link = l(t('Return to the examples page.'), 'ctools_ajax_sample'); @@ -732,7 +732,7 @@ function ctools_ajax_sample_jump_menu_page() { } /** - * Provide a form for an example ajax modal button + * Provide a form for an example ajax modal button. */ function ctools_ajax_sample_ajax_button_form() { $form = array(); diff --git a/sites/all/modules/contrib/dev/ctools/ctools_custom_content/ctools_custom_content.info b/sites/all/modules/contrib/dev/ctools/ctools_custom_content/ctools_custom_content.info index d9c4567..2bc7348 100644 --- a/sites/all/modules/contrib/dev/ctools/ctools_custom_content/ctools_custom_content.info +++ b/sites/all/modules/contrib/dev/ctools/ctools_custom_content/ctools_custom_content.info @@ -4,9 +4,8 @@ core = 7.x package = Chaos tool suite dependencies[] = ctools -; Information added by drupal.org packaging script on 2013-04-03 -version = "7.x-1.3" +; Information added by Drupal.org packaging script on 2019-02-08 +version = "7.x-1.15" core = "7.x" project = "ctools" -datestamp = "1365013512" - +datestamp = "1549603691" diff --git a/sites/all/modules/contrib/dev/ctools/ctools_custom_content/ctools_custom_content.install b/sites/all/modules/contrib/dev/ctools/ctools_custom_content/ctools_custom_content.install index b4512f2..dcf87e7 100644 --- a/sites/all/modules/contrib/dev/ctools/ctools_custom_content/ctools_custom_content.install +++ b/sites/all/modules/contrib/dev/ctools/ctools_custom_content/ctools_custom_content.install @@ -1,5 +1,9 @@ type == 'custom') { + if (!isset($pane->configuration['name'])) { + $name_of_pane = $pane->subtype; + } + else { + $name_of_pane = $pane->configuration['name']; + } + + $links['top']['edit_custom_content'] = array( + 'title' => t('Edit custom content pane'), + 'href' => url('admin/structure/ctools-content/list/' . $name_of_pane . '/edit', array('absolute' => TRUE)), + 'attributes' => array('target' => array('_blank')), + ); + } +} + /** * Create callback for creating a new CTools custom content type. * @@ -60,7 +80,7 @@ function ctools_custom_content_panels_dashboard_blocks(&$vars) { 'description' => t('Custom content panes are basic HTML you enter that can be reused in all of your panels.'), ); - // Load all mini panels and their displays. + // Load all mini panels and their displays. ctools_include('export'); $items = ctools_export_crud_load_all('ctools_custom_content'); $count = 0; diff --git a/sites/all/modules/contrib/dev/ctools/ctools_custom_content/plugins/export_ui/ctools_custom_content.inc b/sites/all/modules/contrib/dev/ctools/ctools_custom_content/plugins/export_ui/ctools_custom_content.inc index 467dc58..c7933bc 100644 --- a/sites/all/modules/contrib/dev/ctools/ctools_custom_content/plugins/export_ui/ctools_custom_content.inc +++ b/sites/all/modules/contrib/dev/ctools/ctools_custom_content/plugins/export_ui/ctools_custom_content.inc @@ -1,5 +1,9 @@ 'ctools_custom_content', 'access' => 'administer custom content', @@ -17,4 +21,3 @@ $plugin = array( 'handler' => 'ctools_custom_content_ui', ); - diff --git a/sites/all/modules/contrib/dev/ctools/ctools_custom_content/plugins/export_ui/ctools_custom_content_ui.class.php b/sites/all/modules/contrib/dev/ctools/ctools_custom_content/plugins/export_ui/ctools_custom_content_ui.class.php index 56fe4b2..e56f7a7 100644 --- a/sites/all/modules/contrib/dev/ctools/ctools_custom_content/plugins/export_ui/ctools_custom_content_ui.class.php +++ b/sites/all/modules/contrib/dev/ctools/ctools_custom_content/plugins/export_ui/ctools_custom_content_ui.class.php @@ -2,7 +2,7 @@ class ctools_custom_content_ui extends ctools_export_ui { - function edit_form(&$form, &$form_state) { + public function edit_form(&$form, &$form_state) { // Correct for an error that came in because filter format changed. if (is_array($form_state['item']->settings['body'])) { $form_state['item']->settings['format'] = $form_state['item']->settings['body']['format']; @@ -23,6 +23,22 @@ class ctools_custom_content_ui extends ctools_export_ui { '#title' => t('Title'), ); + $form['title_heading'] = array( + '#title' => t('Title heading'), + '#type' => 'select', + '#default_value' => isset($form_state['item']->settings['title_heading']) ? $form_state['item']->settings['title_heading'] : 'h2', + '#options' => array( + 'h1' => t('h1'), + 'h2' => t('h2'), + 'h3' => t('h3'), + 'h4' => t('h4'), + 'h5' => t('h5'), + 'h6' => t('h6'), + 'div' => t('div'), + 'span' => t('span'), + ), + ); + $form['body'] = array( '#type' => 'text_format', '#title' => t('Body'), @@ -38,17 +54,18 @@ class ctools_custom_content_ui extends ctools_export_ui { ); } - function edit_form_submit(&$form, &$form_state) { + public function edit_form_submit(&$form, &$form_state) { parent::edit_form_submit($form, $form_state); // Since items in our settings are not in the schema, we have to do these manually: $form_state['item']->settings['title'] = $form_state['values']['title']; + $form_state['item']->settings['title_heading'] = $form_state['values']['title_heading']; $form_state['item']->settings['body'] = $form_state['values']['body']['value']; $form_state['item']->settings['format'] = $form_state['values']['body']['format']; $form_state['item']->settings['substitute'] = $form_state['values']['substitute']; } - function list_form(&$form, &$form_state) { + public function list_form(&$form, &$form_state) { parent::list_form($form, $form_state); $options = array('all' => t('- All -')); @@ -65,7 +82,7 @@ class ctools_custom_content_ui extends ctools_export_ui { ); } - function list_filter($form_state, $item) { + public function list_filter($form_state, $item) { if ($form_state['values']['category'] != 'all' && $form_state['values']['category'] != $item->category) { return TRUE; } @@ -73,7 +90,7 @@ class ctools_custom_content_ui extends ctools_export_ui { return parent::list_filter($form_state, $item); } - function list_sort_options() { + public function list_sort_options() { return array( 'disabled' => t('Enabled, title'), 'title' => t('Title'), @@ -83,21 +100,25 @@ class ctools_custom_content_ui extends ctools_export_ui { ); } - function list_build_row($item, &$form_state, $operations) { - // Set up sorting + public function list_build_row($item, &$form_state, $operations) { + // Set up sorting. switch ($form_state['values']['order']) { case 'disabled': $this->sorts[$item->name] = empty($item->disabled) . $item->admin_title; break; + case 'title': $this->sorts[$item->name] = $item->admin_title; break; + case 'name': $this->sorts[$item->name] = $item->name; break; + case 'category': $this->sorts[$item->name] = $item->category; break; + case 'storage': $this->sorts[$item->name] = $item->type . $item->admin_title; break; @@ -117,7 +138,7 @@ class ctools_custom_content_ui extends ctools_export_ui { ); } - function list_table_header() { + public function list_table_header() { return array( array('data' => t('Name'), 'class' => array('ctools-export-ui-name')), array('data' => t('Title'), 'class' => array('ctools-export-ui-title')), diff --git a/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/README.txt b/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/README.txt index 42edcdc..2f9b0ff 100644 --- a/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/README.txt +++ b/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/README.txt @@ -11,4 +11,4 @@ There are a number of ways to profit from this: 2. There is a sample panel. You can access it at /ctools_plugin_example/xxxx to see how it works. -3. There is Advanced Help at admin/advanced_help/ctools_plugin_example. \ No newline at end of file +3. There is Advanced Help at admin/advanced_help/ctools_plugin_example. diff --git a/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/ctools_plugin_example.info b/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/ctools_plugin_example.info index e4fa534..3f268db 100644 --- a/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/ctools_plugin_example.info +++ b/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/ctools_plugin_example.info @@ -7,9 +7,8 @@ dependencies[] = page_manager dependencies[] = advanced_help core = 7.x -; Information added by drupal.org packaging script on 2013-04-03 -version = "7.x-1.3" +; Information added by Drupal.org packaging script on 2019-02-08 +version = "7.x-1.15" core = "7.x" project = "ctools" -datestamp = "1365013512" - +datestamp = "1549603691" diff --git a/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/ctools_plugin_example.module b/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/ctools_plugin_example.module index 01d5338..a9a6080 100644 --- a/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/ctools_plugin_example.module +++ b/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/ctools_plugin_example.module @@ -1,9 +1,8 @@ .pages_default.inc + * .pages_default.inc. */ function ctools_plugin_example_ctools_plugin_api($module, $api) { // @todo -- this example should explain how to put it in a different file. @@ -71,7 +70,8 @@ function ctools_plugin_example_ctools_plugin_api($module, $api) { } /** - * Just provide an explanation page for the admin section + * Just provide an explanation page for the admin section. + * * @return unknown_type */ function ctools_plugin_example_explanation_page() { diff --git a/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/ctools_plugin_example.pages_default.inc b/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/ctools_plugin_example.pages_default.inc index 10a7619..ed3ffd7 100644 --- a/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/ctools_plugin_example.pages_default.inc +++ b/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/ctools_plugin_example.pages_default.inc @@ -6,7 +6,7 @@ */ /** - * Default panels pages for CTools Plugin Example + * Default panels pages for CTools Plugin Example. * * To pick up this file, your module needs to implement * hook_ctools_plugin_api() - See ctools_plugin_example_ctools_plugin_api() in @@ -23,12 +23,10 @@ * @return * Array of pages, normally exported from Panels. */ - function ctools_plugin_example_default_page_manager_pages() { - // begin exported panel. - - $page = new stdClass; + // Begin exported panel. + $page = new stdClass(); $page->disabled = FALSE; /* Edit this to true to make a default page disabled initially */ $page->api_version = 1; $page->name = 'ctools_plugin_example'; @@ -37,31 +35,31 @@ function ctools_plugin_example_default_page_manager_pages() { $page->admin_description = 'This panel provides no functionality to a working Drupal system. It\'s intended to display the various sample plugins provided by the CTools Plugin Example module. '; $page->path = 'ctools_plugin_example/%sc'; $page->access = array( - 'logic' => 'and', + 'logic' => 'and', ); $page->menu = array( - 'type' => 'normal', - 'title' => 'CTools plugin example', - 'name' => 'navigation', - 'weight' => '0', - 'parent' => array( - 'type' => 'none', - 'title' => '', + 'type' => 'normal', + 'title' => 'CTools plugin example', 'name' => 'navigation', 'weight' => '0', - ), + 'parent' => array( + 'type' => 'none', + 'title' => '', + 'name' => 'navigation', + 'weight' => '0', + ), ); $page->arguments = array( - 'sc' => array( - 'id' => 2, - 'identifier' => 'simplecontext-arg', - 'name' => 'simplecontext_arg', - 'settings' => array(), - ), + 'sc' => array( + 'id' => 2, + 'identifier' => 'simplecontext-arg', + 'name' => 'simplecontext_arg', + 'settings' => array(), + ), ); $page->conf = array(); $page->default_handlers = array(); - $handler = new stdClass; + $handler = new stdClass(); $handler->disabled = FALSE; /* Edit this to true to make a default handler disabled initially */ $handler->api_version = 1; $handler->name = 'page_ctools_panel_context'; @@ -70,44 +68,45 @@ function ctools_plugin_example_default_page_manager_pages() { $handler->handler = 'panel_context'; $handler->weight = 0; $handler->conf = array( - 'title' => 'Panel', - 'no_blocks' => FALSE, - 'css_id' => '', - 'css' => '', - 'contexts' => array( - '0' => array( - 'name' => 'simplecontext', - 'id' => 1, - 'identifier' => 'Configured simplecontext (not from argument)', - 'keyword' => 'configured_simplecontext', - 'context_settings' => array( - 'sample_simplecontext_setting' => 'default simplecontext setting', - ), - ), - ), - 'relationships' => array( - '0' => array( - 'context' => 'argument_simplecontext_arg_2', - 'name' => 'relcontext_from_simplecontext', - 'id' => 1, - 'identifier' => 'Relcontext from simplecontext (from relationship)', - 'keyword' => 'relcontext', - ), - ), - 'access' => array( - 'logic' => 'and', - ), + 'title' => 'Panel', + 'no_blocks' => FALSE, + 'css_id' => '', + 'css' => '', + 'contexts' => array( + '0' => array( + 'name' => 'simplecontext', + 'id' => 1, + 'identifier' => 'Configured simplecontext (not from argument)', + 'keyword' => 'configured_simplecontext', + 'context_settings' => array( + 'sample_simplecontext_setting' => 'default simplecontext setting', + ), + ), + ), + 'relationships' => array( + '0' => array( + 'context' => 'argument_simplecontext_arg_2', + 'name' => 'relcontext_from_simplecontext', + 'id' => 1, + 'identifier' => 'Relcontext from simplecontext (from relationship)', + 'keyword' => 'relcontext', + ), + ), + 'access' => array( + 'logic' => 'and', + ), + 'pipeline' => 'standard', ); - $display = new panels_display; + $display = new panels_display(); $display->layout = 'threecol_33_34_33_stacked'; $display->layout_settings = array(); $display->panel_settings = array( - 'style' => 'rounded_corners', - 'style_settings' => array( - 'default' => array( - 'corner_location' => 'pane', - ), - ), + 'style' => 'rounded_corners', + 'style_settings' => array( + 'default' => array( + 'corner_location' => 'pane', + ), + ), ); $display->cache = array(); $display->title = 'CTools plugin example panel'; @@ -115,7 +114,7 @@ function ctools_plugin_example_default_page_manager_pages() { $display->title_pane = 1; $display->content = array(); $display->panels = array(); - $pane = new stdClass; + $pane = new stdClass(); $pane->pid = 'new-1'; $pane->panel = 'left'; $pane->type = 'no_context_content_type'; @@ -135,7 +134,7 @@ function ctools_plugin_example_default_page_manager_pages() { $pane->position = 0; $display->content['new-1'] = $pane; $display->panels['left'][0] = 'new-1'; - $pane = new stdClass; + $pane = new stdClass(); $pane->pid = 'new-2'; $pane->panel = 'left'; $pane->type = 'custom'; @@ -148,15 +147,15 @@ function ctools_plugin_example_default_page_manager_pages() { 'settings' => array( 'greater_than' => '1', 'arg_length' => '4', - ), + ), 'context' => 'argument_simplecontext_arg_2', - ), - ), + ), + ), ); $pane->configuration = array( 'title' => 'Long Arg Visibility Block', 'body' => 'This block will be here when the argument is longer than configured arg length. It uses the \'arg_length\' access plugin to test against the length of the argument used for Simplecontext.', - 'format' => '1', + 'format' => 'filtered_html', 'substitute' => 1, ); $pane->cache = array(); @@ -166,7 +165,7 @@ function ctools_plugin_example_default_page_manager_pages() { $pane->position = 1; $display->content['new-2'] = $pane; $display->panels['left'][1] = 'new-2'; - $pane = new stdClass; + $pane = new stdClass(); $pane->pid = 'new-3'; $pane->panel = 'left'; $pane->type = 'custom'; @@ -179,15 +178,15 @@ function ctools_plugin_example_default_page_manager_pages() { 'settings' => array( 'greater_than' => '0', 'arg_length' => '4', - ), + ), 'context' => 'argument_simplecontext_arg_2', - ), - ), + ), + ), ); $pane->configuration = array( 'title' => 'Short Arg Visibility', 'body' => 'This block appears when the simplecontext argument is less than the configured length.', - 'format' => '1', + 'format' => 'filtered_html', 'substitute' => 1, ); $pane->cache = array(); @@ -197,7 +196,7 @@ function ctools_plugin_example_default_page_manager_pages() { $pane->position = 2; $display->content['new-3'] = $pane; $display->panels['left'][2] = 'new-3'; - $pane = new stdClass; + $pane = new stdClass(); $pane->pid = 'new-4'; $pane->panel = 'middle'; $pane->type = 'simplecontext_content_type'; @@ -241,7 +240,7 @@ function ctools_plugin_example_default_page_manager_pages() { $pane->position = 0; $display->content['new-4'] = $pane; $display->panels['middle'][0] = 'new-4'; - $pane = new stdClass; + $pane = new stdClass(); $pane->pid = 'new-5'; $pane->panel = 'middle'; $pane->type = 'simplecontext_content_type'; @@ -285,7 +284,7 @@ function ctools_plugin_example_default_page_manager_pages() { $pane->position = 1; $display->content['new-5'] = $pane; $display->panels['middle'][1] = 'new-5'; - $pane = new stdClass; + $pane = new stdClass(); $pane->pid = 'new-6'; $pane->panel = 'middle'; $pane->type = 'custom'; @@ -299,7 +298,7 @@ function ctools_plugin_example_default_page_manager_pages() { item1 is %sc:item1 item2 is %sc:item2 description is %sc:description', - 'format' => '1', + 'format' => 'filtered_html', 'substitute' => 1, ); $pane->cache = array(); @@ -309,7 +308,7 @@ function ctools_plugin_example_default_page_manager_pages() { $pane->position = 2; $display->content['new-6'] = $pane; $display->panels['middle'][2] = 'new-6'; - $pane = new stdClass; + $pane = new stdClass(); $pane->pid = 'new-7'; $pane->panel = 'right'; $pane->type = 'relcontext_content_type'; @@ -353,7 +352,7 @@ function ctools_plugin_example_default_page_manager_pages() { $pane->position = 0; $display->content['new-7'] = $pane; $display->panels['right'][0] = 'new-7'; - $pane = new stdClass; + $pane = new stdClass(); $pane->pid = 'new-8'; $pane->panel = 'top'; $pane->type = 'custom'; @@ -365,7 +364,7 @@ function ctools_plugin_example_default_page_manager_pages() { 'body' => 'The CTools Plugin Example module (and this panel page) are just here to demonstrate how to build CTools plugins. ', - 'format' => '2', + 'format' => 'full_html', 'substitute' => 1, ); $pane->cache = array(); @@ -378,12 +377,11 @@ function ctools_plugin_example_default_page_manager_pages() { $handler->conf['display'] = $display; $page->default_handlers[$handler->name] = $handler; - // end of exported panel. + // End of exported panel. $pages['ctools_plugin_example_demo_page'] = $page; - // begin exported panel - - $page = new stdClass; + // Begin exported panel. + $page = new stdClass(); $page->disabled = FALSE; /* Edit this to true to make a default page disabled initially */ $page->api_version = 1; $page->name = 'ctools_plugin_example_base'; @@ -396,7 +394,7 @@ function ctools_plugin_example_default_page_manager_pages() { $page->arguments = array(); $page->conf = array(); $page->default_handlers = array(); - $handler = new stdClass; + $handler = new stdClass(); $handler->disabled = FALSE; /* Edit this to true to make a default handler disabled initially */ $handler->api_version = 1; $handler->name = 'page_ctools_plugin_example_base_panel_context'; @@ -405,14 +403,15 @@ function ctools_plugin_example_default_page_manager_pages() { $handler->handler = 'panel_context'; $handler->weight = 0; $handler->conf = array( - 'title' => 'Panel', - 'no_blocks' => FALSE, - 'css_id' => '', - 'css' => '', - 'contexts' => array(), - 'relationships' => array(), + 'title' => 'Panel', + 'no_blocks' => FALSE, + 'css_id' => '', + 'css' => '', + 'contexts' => array(), + 'relationships' => array(), + 'pipeline' => 'standard', ); - $display = new panels_display; + $display = new panels_display(); $display->layout = 'onecol'; $display->layout_settings = array(); $display->panel_settings = array(); @@ -421,7 +420,7 @@ function ctools_plugin_example_default_page_manager_pages() { $display->hide_title = FALSE; $display->content = array(); $display->panels = array(); - $pane = new stdClass; + $pane = new stdClass(); $pane->pid = 'new-1'; $pane->panel = 'middle'; $pane->type = 'custom'; @@ -431,7 +430,7 @@ function ctools_plugin_example_default_page_manager_pages() { $pane->configuration = array( 'title' => 'Use this page with an argument', 'body' => 'This demo page works if you use an argument, like ctools_plugin_example/xxxxx.', - 'format' => '1', + 'format' => 'filtered_html', 'substitute' => NULL, ); $pane->cache = array(); @@ -443,9 +442,8 @@ function ctools_plugin_example_default_page_manager_pages() { $display->panels['middle'][0] = 'new-1'; $handler->conf['display'] = $display; $page->default_handlers[$handler->name] = $handler; - // end exported panel. - + // End exported panel. $pages['base_page'] = $page; return $pages; -} \ No newline at end of file +} diff --git a/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/plugins/access/arg_length.inc b/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/plugins/access/arg_length.inc index 2a09eea..3b5d863 100644 --- a/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/plugins/access/arg_length.inc +++ b/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/plugins/access/arg_length.inc @@ -59,7 +59,8 @@ function ctools_plugin_example_arg_length_ctools_access_check($conf, $context) { */ function ctools_plugin_example_arg_length_ctools_access_summary($conf, $context) { return t('Simpletext argument must be !comp @length characters', - array('!comp' => $conf['greater_than'] ? 'greater than' : 'less than or equal to', - '@length' => $conf['arg_length'])); + array( + '!comp' => $conf['greater_than'] ? 'greater than' : 'less than or equal to', + '@length' => $conf['arg_length'], + )); } - diff --git a/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/plugins/access/example_role.inc b/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/plugins/access/example_role.inc index bbe364c..75721e8 100644 --- a/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/plugins/access/example_role.inc +++ b/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/plugins/access/example_role.inc @@ -4,7 +4,7 @@ * @file * Plugin to provide access control based upon role membership. * This is directly from the ctools module, but serves as a good - * example of an access plugin + * example of an access plugin. */ /** @@ -73,4 +73,3 @@ function ctools_plugin_example_example_role_ctools_access_summary($conf, $contex } return format_plural(count($names), '@identifier must have role "@roles"', '@identifier can be one of "@roles"', array('@roles' => implode(', ', $names), '@identifier' => $context->identifier)); } - diff --git a/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/plugins/arguments/simplecontext_arg.inc b/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/plugins/arguments/simplecontext_arg.inc index 51c7c60..7fb732c 100644 --- a/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/plugins/arguments/simplecontext_arg.inc +++ b/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/plugins/arguments/simplecontext_arg.inc @@ -2,7 +2,6 @@ /** * @file - * * Sample plugin to provide an argument handler for a simplecontext. * * Given any argument to the page, simplecontext will get it @@ -18,14 +17,12 @@ */ $plugin = array( 'title' => t("Simplecontext arg"), - // keyword to use for %substitution + // Keyword to use for %substitution. 'keyword' => 'simplecontext', 'description' => t('Creates a "simplecontext" from the arg.'), 'context' => 'simplecontext_arg_context', - // 'settings form' => 'simplecontext_arg_settings_form', - // placeholder_form is used in panels preview, for example, so we can - // preview without getting the arg from a URL + // preview without getting the arg from a URL. 'placeholder form' => array( '#type' => 'textfield', '#description' => t('Enter the simplecontext arg'), diff --git a/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/plugins/content_types/no_context_content_type.inc b/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/plugins/content_types/no_context_content_type.inc index 3c02ab8..48ce0f5 100644 --- a/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/plugins/content_types/no_context_content_type.inc +++ b/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/plugins/content_types/no_context_content_type.inc @@ -5,7 +5,6 @@ * "No context" sample content type. It operates with no context at all. It would * be basically the same as a 'custom content' block, but it's not even that * sophisticated. - * */ /** @@ -33,7 +32,7 @@ $plugin = array( 'icon' => 'icon_example.png', 'category' => array(t('CTools Examples'), -9), - // this example does not provide 'admin info', which would populate the + // This example does not provide 'admin info', which would populate the // panels builder page preview. ); @@ -56,7 +55,7 @@ function no_context_content_type_render($subtype, $conf, $args, $context) { $ctools_help = theme('advanced_help_topic', array('module' => 'ctools', 'topic' => 'plugins', 'type' => 'title')); $ctools_plugin_example_help = theme('advanced_help_topic', array('module' => 'ctools_plugin_example', 'topic' => 'Chaos-Tools--CTools--Plugin-Examples', 'type' => 'title')); - // The title actually used in rendering + // The title actually used in rendering. $block->title = check_plain("No-context content type"); $block->content = t("
    Welcome to the CTools Plugin Example demonstration content type. @@ -84,7 +83,6 @@ function no_context_content_type_render($subtype, $conf, $args, $context) { * Note that if we had not provided an entry for this in hook_content_types, * this could have had the default name * ctools_plugin_example_no_context_content_type_edit_form. - * */ function no_context_content_type_edit_form($form, &$form_state) { $conf = $form_state['conf']; diff --git a/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/plugins/content_types/relcontext_content_type.inc b/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/plugins/content_types/relcontext_content_type.inc index bf54dce..ced6411 100644 --- a/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/plugins/content_types/relcontext_content_type.inc +++ b/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/plugins/content_types/relcontext_content_type.inc @@ -1,6 +1,5 @@ array(t('CTools Examples'), -9), 'edit form' => 'relcontext_edit_form', - // this example does not provide 'admin info', which would populate the + // This example does not provide 'admin info', which would populate the // panels builder page preview. - ); /** @@ -60,7 +58,7 @@ function relcontext_content_type_render($subtype, $conf, $args, $context) { In our case, the configuration form (\$conf) has just one field, 'config_item_1; and it's configured with: "); - if (!empty($conf)) { + if (!empty($conf)) { $block->content .= '
    ' . var_export($conf['config_item_1'], TRUE) . '
    '; } if (!empty($context)) { @@ -77,7 +75,6 @@ function relcontext_content_type_render($subtype, $conf, $args, $context) { /** * 'Edit' callback for the content type. * This example just returns a form. - * */ function relcontext_edit_form($form, &$form_state) { $conf = $form_state['conf']; diff --git a/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/plugins/content_types/simplecontext_content_type.inc b/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/plugins/content_types/simplecontext_content_type.inc index a308683..e34fa18 100644 --- a/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/plugins/content_types/simplecontext_content_type.inc +++ b/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/plugins/content_types/simplecontext_content_type.inc @@ -1,6 +1,5 @@ 'simplecontext_content_type_edit_form', 'admin title' => 'ctools_plugin_example_simplecontext_content_type_admin_title', - // presents a block which is used in the preview of the data. + // Presents a block which is used in the preview of the data. // Pn Panels this is the preview pane shown on the panels building page. 'admin info' => 'ctools_plugin_example_simplecontext_content_type_admin_info', 'category' => array(t('CTools Examples'), -9), @@ -103,7 +101,6 @@ function simplecontext_content_type_render($subtype, $conf, $args, $context) { /** * 'Edit' callback for the content type. * This example just returns a form. - * */ function simplecontext_content_type_edit_form($form, &$form_state) { $conf = $form_state['conf']; diff --git a/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/plugins/contexts/relcontext.inc b/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/plugins/contexts/relcontext.inc index 0c7ef11..61e25db 100644 --- a/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/plugins/contexts/relcontext.inc +++ b/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/plugins/contexts/relcontext.inc @@ -80,4 +80,3 @@ function relcontext_settings_form($conf, $external = FALSE) { ); return $form; } - diff --git a/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/plugins/contexts/simplecontext.inc b/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/plugins/contexts/simplecontext.inc index e19a842..0ee4658 100644 --- a/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/plugins/contexts/simplecontext.inc +++ b/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/plugins/contexts/simplecontext.inc @@ -1,10 +1,8 @@ t("Simplecontext"), 'description' => t('A single "simplecontext" context, or data element.'), - 'context' => 'ctools_plugin_example_context_create_simplecontext', // func to create context +// Func to create context. + 'context' => 'ctools_plugin_example_context_create_simplecontext', 'context name' => 'simplecontext', 'settings form' => 'simplecontext_settings_form', 'keyword' => 'simplecontext', @@ -69,7 +68,7 @@ function ctools_plugin_example_context_create_simplecontext($empty, $data = NULL // This is used for keyword. $context->title = $data; $context->argument = $data; - // Make up a bogus context + // Make up a bogus context. $context->data = new stdClass(); $context->data->item1 = t("Item1"); $context->data->item2 = t("Item2"); @@ -102,8 +101,6 @@ function simplecontext_settings_form($conf, $external = FALSE) { return $form; } - - /** * Provide a list of sub-keywords. * @@ -125,10 +122,11 @@ function simplecontext_convert($context, $type) { switch ($type) { case 'item1': return $context->data->item1; + case 'item2': return $context->data->item2; + case 'description': return $context->data->description; } } - diff --git a/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/plugins/panels.pages.inc b/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/plugins/panels.pages.inc index d3022af..25422cf 100644 --- a/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/plugins/panels.pages.inc +++ b/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/plugins/panels.pages.inc @@ -19,24 +19,24 @@ function ctools_plugin_example_default_panel_pages() { $page->load_flags = 1; $page->css_id = ''; $page->arguments = array( - 0 => + 0 => array( - 'name' => 'simplecontext_arg', - 'id' => 1, - 'default' => '404', - 'title' => '', - 'identifier' => 'Simplecontext arg', - 'keyword' => 'simplecontext', + 'name' => 'simplecontext_arg', + 'id' => 1, + 'default' => '404', + 'title' => '', + 'identifier' => 'Simplecontext arg', + 'keyword' => 'simplecontext', ), ); $page->relationships = array( - 0 => + 0 => array( - 'context' => 'argument_simplecontext_arg_1', - 'name' => 'relcontext_from_simplecontext', - 'id' => 1, - 'identifier' => 'Relcontext from Simplecontext', - 'keyword' => 'relcontext', + 'context' => 'argument_simplecontext_arg_1', + 'name' => 'relcontext_from_simplecontext', + 'id' => 1, + 'identifier' => 'Relcontext from Simplecontext', + 'keyword' => 'relcontext', ), ); $page->no_blocks = '0'; @@ -58,14 +58,14 @@ function ctools_plugin_example_default_panel_pages() { $pane->subtype = 'custom'; $pane->access = array(); $pane->configuration = array( - 'style' => 'default', - 'override_title' => 0, - 'override_title_text' => '', - 'css_id' => '', - 'css_class' => '', - 'title' => '"No Context Item"', - 'body' => 'The "no context item" content type is here to demonstrate that you can create a content_type that does not require a context. This is probably the same as just creating a custom php block on the fly, and might serve the same purpose.', - 'format' => '1', + 'style' => 'default', + 'override_title' => 0, + 'override_title_text' => '', + 'css_id' => '', + 'css_class' => '', + 'title' => '"No Context Item"', + 'body' => 'The "no context item" content type is here to demonstrate that you can create a content_type that does not require a context. This is probably the same as just creating a custom php block on the fly, and might serve the same purpose.', + 'format' => '1', ); $pane->cache = array(); $display->content['new-1'] = $pane; @@ -78,14 +78,14 @@ function ctools_plugin_example_default_panel_pages() { $pane->subtype = 'description'; $pane->access = array(); $pane->configuration = array( - 'style' => 'default', - 'override_title' => 0, - 'override_title_text' => '', - 'css_id' => '', - 'css_class' => '', - 'item1' => 'one', - 'item2' => 'two', - 'item3' => 'three', + 'style' => 'default', + 'override_title' => 0, + 'override_title_text' => '', + 'css_id' => '', + 'css_class' => '', + 'item1' => 'one', + 'item2' => 'two', + 'item3' => 'three', ); $pane->cache = array(); $display->content['new-2'] = $pane; @@ -98,16 +98,16 @@ function ctools_plugin_example_default_panel_pages() { $pane->subtype = 'custom'; $pane->access = array(); $pane->configuration = array( - 'style' => 'default', - 'override_title' => 0, - 'override_title_text' => '', - 'css_id' => '', - 'css_class' => '', - 'title' => 'Simplecontext', - 'body' => 'The "Simplecontext" content and content type demonstrate a very basic context and how to display it. + 'style' => 'default', + 'override_title' => 0, + 'override_title_text' => '', + 'css_id' => '', + 'css_class' => '', + 'title' => 'Simplecontext', + 'body' => 'The "Simplecontext" content and content type demonstrate a very basic context and how to display it. Simplecontext includes configuration, so it can get info from the config. It can also get its information to run from a simplecontext context, generated either from an arg to the panels page or via explicitly adding a context to the page.', - 'format' => '1', + 'format' => '1', ); $pane->cache = array(); $display->content['new-3'] = $pane; @@ -119,17 +119,17 @@ function ctools_plugin_example_default_panel_pages() { $pane->shown = '1'; $pane->subtype = 'description'; $pane->access = array( - 0 => '2', - 1 => '4', + 0 => '2', + 1 => '4', ); $pane->configuration = array( - 'context' => 'argument_simplecontext_arg_1', - 'style' => 'default', - 'override_title' => 0, - 'override_title_text' => '', - 'css_id' => '', - 'css_class' => '', - 'config_item_1' => 'simplecontext called from arg', + 'context' => 'argument_simplecontext_arg_1', + 'style' => 'default', + 'override_title' => 0, + 'override_title_text' => '', + 'css_id' => '', + 'css_class' => '', + 'config_item_1' => 'simplecontext called from arg', ); $pane->cache = array(); $display->content['new-4'] = $pane; @@ -142,14 +142,14 @@ function ctools_plugin_example_default_panel_pages() { $pane->subtype = 'custom'; $pane->access = array(); $pane->configuration = array( - 'style' => 'default', - 'override_title' => 0, - 'override_title_text' => '', - 'css_id' => '', - 'css_class' => '', - 'title' => 'Relcontext', - 'body' => 'The relcontext content_type gets its data from a relcontext, which is an example of a relationship. This panel should be run with an argument like "/xxx", which allows the simplecontext to get its context, and then the relcontext is configured in this panel to get (create) its data from the simplecontext.', - 'format' => '1', + 'style' => 'default', + 'override_title' => 0, + 'override_title_text' => '', + 'css_id' => '', + 'css_class' => '', + 'title' => 'Relcontext', + 'body' => 'The relcontext content_type gets its data from a relcontext, which is an example of a relationship. This panel should be run with an argument like "/xxx", which allows the simplecontext to get its context, and then the relcontext is configured in this panel to get (create) its data from the simplecontext.', + 'format' => '1', ); $pane->cache = array(); $display->content['new-5'] = $pane; @@ -162,13 +162,13 @@ function ctools_plugin_example_default_panel_pages() { $pane->subtype = 'description'; $pane->access = array(); $pane->configuration = array( - 'context' => 'relationship_relcontext_from_simplecontext_1', - 'style' => 'default', - 'override_title' => 0, - 'override_title_text' => '', - 'css_id' => '', - 'css_class' => '', - 'config_item_1' => 'default1', + 'context' => 'relationship_relcontext_from_simplecontext_1', + 'style' => 'default', + 'override_title' => 0, + 'override_title_text' => '', + 'css_id' => '', + 'css_class' => '', + 'config_item_1' => 'default1', ); $pane->cache = array(); $display->content['new-6'] = $pane; @@ -181,13 +181,13 @@ function ctools_plugin_example_default_panel_pages() { $pane->subtype = 'custom_php'; $pane->access = array(); $pane->configuration = array( - 'style' => 'default', - 'override_title' => 0, - 'override_title_text' => '', - 'css_id' => '', - 'css_class' => '', - 'title' => '', - 'body' => '$arg = arg(1); + 'style' => 'default', + 'override_title' => 0, + 'override_title_text' => '', + 'css_id' => '', + 'css_class' => '', + 'title' => '', + 'body' => '$arg = arg(1); $arg0 = arg(0); if (!$arg) { $block->content = <<displays = array(); $pages['ctools_plugin_example'] = $page; - return $pages; } diff --git a/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/plugins/relationships/relcontext_from_simplecontext.inc b/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/plugins/relationships/relcontext_from_simplecontext.inc index 6224621..087d5ec 100644 --- a/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/plugins/relationships/relcontext_from_simplecontext.inc +++ b/sites/all/modules/contrib/dev/ctools/ctools_plugin_example/plugins/relationships/relcontext_from_simplecontext.inc @@ -1,9 +1,7 @@ 'Space separated list of exportables you want to view.', ), 'options' => array( - 'indent' => 'The string to use for indentation when dispalying the exportable export code. Defaults to \'\'.', + 'indent' => 'The string to use for indentation when displaying the exportable export code. Defaults to \'\'.', 'no-colour' => 'Remove any colour formatting from export string output. Ideal if you are sending the output of this command to a file.', 'module' => $module_text, 'all' => $all_text, @@ -140,13 +140,14 @@ function ctools_drush_help($section) { switch ($section) { case 'meta:ctools:title': return dt('CTools commands'); + case 'meta:entity:summary': return dt('CTools drush commands.'); } } /** - * Drush callback: export + * Drush callback: export. */ function ctools_drush_export($module = 'foo') { $error = FALSE; @@ -338,7 +339,7 @@ function _ctools_drush_selection_screen(array $tables = array()) { $build[$table_choice]['count'] = count($multi_select); $selections[$table_choice] = array(); foreach ($multi_select as $key) { - $selections[$table_choice][$key] = $key; + $selections[$table_choice][$key] = $key; } } } @@ -445,11 +446,13 @@ function ctools_drush_export_info() { } } } + /** * Drush callback: Acts as the hub for all op commands to keep * all arg handling etc in one place. */ function ctools_drush_export_op_command() { + $args = func_get_args(); // Get all info for the current drush command. $command = drush_get_command(); $op = ''; @@ -457,17 +460,20 @@ function ctools_drush_export_op_command() { switch ($command['command']) { case 'ctools-export-view': $op = 'view'; - break; + break; + case 'ctools-export-revert': // Revert is same as deleting. As any objects in the db are deleted. $op = 'delete'; - break; + break; + case 'ctools-export-enable': $op = 'enable'; - break; + break; + case 'ctools-export-disable': $op = 'disable'; - break; + break; } if (!$op) { @@ -475,7 +481,7 @@ function ctools_drush_export_op_command() { } if (drush_get_option('all', FALSE)) { - $info = _ctools_drush_export_info('', TRUE); + $info = _ctools_drush_export_info(array(), TRUE); $exportable_info = $info['exportables']; $all = drush_confirm(dt('Are you sure you would like to !op all exportables on the system?', @@ -490,7 +496,6 @@ function ctools_drush_export_op_command() { } } else { - $args = func_get_args(); // Table name should always be first arg... $table_name = array_shift($args); // Any additional args are assumed to be exportable names. @@ -512,7 +517,6 @@ function ctools_drush_export_op_command() { * @param $op * @param $table_name * @param $exportables - * */ function ctools_drush_export_op($op = '', $table_name = '', $exportables = NULL) { $objects = ctools_export_crud_load_multiple($table_name, array_keys($exportables)); @@ -538,7 +542,7 @@ function ctools_drush_export_op($op = '', $table_name = '', $exportables = NULL) * @param $object_names * * @return - * Array of exportable objects (filtered if necessary, by name etc..) or FALSE if not. + * Array of exportable objects (filtered if necessary, by name etc..) or FALSE if not. */ function _ctools_drush_export_op_command_logic($op = '', $table_name = NULL, array $object_names = array()) { if (!$table_name) { @@ -644,7 +648,7 @@ function _ctools_drush_export_info(array $table_names = array(), $load = FALSE) return array('exportables' => $exportables, 'schemas' => $schemas); } -/* +/** * View a single object. * * @param $table_name @@ -662,7 +666,7 @@ function _ctools_drush_export_view($table_name, $object) { } } -/* +/** * Revert a single object. * * @param $table_name @@ -681,7 +685,7 @@ function _ctools_drush_export_delete($table_name, $object) { } } -/* +/** * Enable a single object. * * @param $table_name @@ -701,7 +705,7 @@ function _ctools_drush_export_enable($table_name, $object) { } } -/* +/** * Disable a single object. * * @param $table_name @@ -723,9 +727,9 @@ function _ctools_drush_export_disable($table_name, $object) { /** * Filter a nested array of exportables by export module. * - * @param $exportables array + * @param array $exportables * Passed by reference. A nested array of exportables, keyed by table name. - * @param $export_module string + * @param string $export_module * The name of the export module providing the exportable. */ function _ctools_drush_export_module_filter($exportables, $export_module) { @@ -778,7 +782,7 @@ function _ctools_drush_object_is_disabled($object) { /** * Determine if an object is enabled. * - * @see _ctools_drush_object_is_disabled. + * @see _ctools_drush_object_is_disabled() */ function _ctools_drush_object_is_enabled($object) { return (empty($object->disabled)) ? TRUE : FALSE; @@ -835,7 +839,7 @@ function _ctools_drush_object_is_not_code_only($object) { * Array of exportables to count. * * @return - * Array of count data containing the following: + * Array of count data containing the following: * 'total' - A total count of all exportables. * 'exportables' - An array of exportable counts per table. */ @@ -867,26 +871,32 @@ function _ctools_drush_filter_exportables($exportables, $filter) { // Show enabled exportables only. case 'enabled': $eval = '_ctools_drush_object_is_disabled'; - break; + break; + // Show disabled exportables only. case 'disabled': $eval = '_ctools_drush_object_is_enabled'; - break; + break; + // Show overridden exportables only. case 'overridden': $eval = '_ctools_drush_object_is_not_overridden'; - break; + break; + // Show database only exportables. case 'database': $eval = '_ctools_drush_object_is_not_db_only'; - break; + break; + // Show code only exportables. case 'code': $eval = '_ctools_drush_object_is_not_code_only'; - break; + break; + // Do nothing. case 'all': break; + default: drush_log(dt('Invalid filter option. Available options are: enabled, disabled, overridden, database, and code.'), 'error'); return; @@ -983,33 +993,42 @@ class shellColours { 'light_gray' => '47', ); + /** + * shellColours constructor. + */ private function __construct() {} - // Returns coloured string + /** + * Returns coloured string. + */ public static function getColouredOutput($string, $foreground_colour = NULL, $background_colour = NULL) { $coloured_string = ""; - // Check if given foreground colour found + // Check if given foreground colour found. if ($foreground_colour) { $coloured_string .= "\033[" . self::$foreground_colours[$foreground_colour] . "m"; } - // Check if given background colour found + // Check if given background colour found. if ($background_colour) { $coloured_string .= "\033[" . self::$background_colours[$background_colour] . "m"; } - // Add string and end colouring - $coloured_string .= $string . "\033[0m"; + // Add string and end colouring. + $coloured_string .= $string . "\033[0m"; return $coloured_string; } - // Returns all foreground colour names + /** + * Returns all foreground colour names. + */ public static function getForegroundColours() { return array_keys(self::$foreground_colours); } - // Returns all background colour names + /** + * Returns all background colour names. + */ public static function getBackgroundColours() { return array_keys(self::$background_colours); } diff --git a/sites/all/modules/contrib/dev/ctools/help/context-content.html b/sites/all/modules/contrib/dev/ctools/help/context-content.html index 8af5df4..c1c6a35 100644 --- a/sites/all/modules/contrib/dev/ctools/help/context-content.html +++ b/sites/all/modules/contrib/dev/ctools/help/context-content.html @@ -116,6 +116,10 @@ The first form will always have required configuration added to it. These forms
    required context [Optional]
    Either a ctools_context_required or ctools_context_optional or array of contexts for this content. If omitted, no contexts are used.
    + +
    create content access [Optional]
    + +
    An optional callback to determine if a user can access this subtype. The callback will receive two arguments, the type and subtype.

    Rendered content

    diff --git a/sites/all/modules/contrib/dev/ctools/help/export.html b/sites/all/modules/contrib/dev/ctools/help/export.html index 3a774e8..573c98a 100644 --- a/sites/all/modules/contrib/dev/ctools/help/export.html +++ b/sites/all/modules/contrib/dev/ctools/help/export.html @@ -154,8 +154,11 @@ function mymodule_schema() {
    Bulk export callback to provide a list of exportable objects to be chosen for bulk exporting. Defaults to $module . '_' . $table . '_list' if the function exists. If it is not, a default listing function will be provided that will make a best effort to list the titles. See ctools_export_default_list().
    to hook code callback
    -
    Function used to generate an export for the bulk export process. This is only necessary if the export is more complicated than simply listing the fields. Defaults to $module . '_' . $table . '_to_hook_code'. +
    Function used to generate an export for the bulk export process. This is only necessary if the export is more complicated than simply listing the fields. Defaults to $module . '_' . $table . '_to_hook_code'.
    +
    boolean
    +
    Explicitly indicate if a table field contains a boolean or not. The Schema API does not model the +difference between a tinyint and a boolean type. Boolean values are stored in tinyint fields. This may cause mismatch errors when exporting a non-boolean value from a tinyint field. Add this to a tinyint field if it contains boolean data and can be exported. Defaults to TRUE.
    create callback
    CRUD callback to use to create a new exportable item in memory. If not provided, the default function will be used. The single argument is a boolean used to determine if defaults should be set on the object. This object will not be written to the database by this callback.
    diff --git a/sites/all/modules/contrib/dev/ctools/help/modal.html b/sites/all/modules/contrib/dev/ctools/help/modal.html index ea823a0..761fd2e 100644 --- a/sites/all/modules/contrib/dev/ctools/help/modal.html +++ b/sites/all/modules/contrib/dev/ctools/help/modal.html @@ -82,7 +82,7 @@ function ctools_modal_text_button($text, $dest, $alt, $class = '') {
  • modalSize: an array of data to control the sizing of the modal. It can contain:
    • type: Either fixed or scale. If fixed, the modal will always be a fixed size. If scale the modal will scale to a percentage of the browser window. Default: scale. -
    • width: If fixed the width in pixels. If scale the percentage of the screen expressed as a number less than zero. (For 80 percent, use .8, for example). Default: .8
    • +
    • width: If fixed the width in pixels. If scale the percentage of the screen expressed as a number less than zero. (For 80 percent, use .8, for example). Default: .8
    • height: If fixed the height in pixels. If scale the percentage of the screen expressed as a number less than zero. (For 80 percent, use .8, for example). Default: .8
    • addWidth: Any additional width to add to the modal in pixels. Only useful if the type is scale. Default: 0
    • addHeight: Any additional height to add to the modal in pixels. Only useful if the type is scale. Default: 0
    • @@ -168,7 +168,7 @@ function ctools_ajax_sample_login($js) {
       function ctools_ajax_hello_world($js) {
         $title = t('Greetings');
      -  $output = '<p>' . t('Hello world') . ''</p>';
      +  $output = '<p>' . t('Hello world') . '</p>';
         if ($js) {
           ctools_modal_render($title, $output);
         }
      diff --git a/sites/all/modules/contrib/dev/ctools/help/plugins-creating.html b/sites/all/modules/contrib/dev/ctools/help/plugins-creating.html
      index 29a9014..6d5b35f 100644
      --- a/sites/all/modules/contrib/dev/ctools/help/plugins-creating.html
      +++ b/sites/all/modules/contrib/dev/ctools/help/plugins-creating.html
      @@ -24,6 +24,18 @@ The following information can be specified for each plugin type:
       
      cache table
      Defaults to: 'cache'
      If 'cache' is TRUE, then this value specifies the cache table where the cached plugin information will be stored.
      +
      classes
      +
      Defaults to: array()
      +
      An array of class identifiers(i.e. plugin array keys) which a plugin of this type uses to provide classes to the CTools autoloader. For example, if classes is set to array('class'), then CTools will search each $plugin['class'] for a class to autoload. Depending of the plugin structure, a class identifier may be either: +
      +
      - a file name:
      +
      the file which holds the class with the name structure as: [filename].[class].php
      +
      in this case the class name can be different than the class identifier
      +
      - the class name:
      +
      if the class is in the same file as the $plugin
      +
      the plugin .inc file can have a different name than the class identifier
      +
      +
      defaults
      Defaults to: array()
      An array of defaults that should be added to each plugin; this can be used to ensure that every plugin has the basic data necessary. These defaults will not ovewrite data supplied by the plugin. This could also be a function name, in which case the callback will be used to provide defaults. NOTE, however, that the callback-based approach is deprecated as it is redundant with the 'process' callback, and as such will be removed in later versions. Consequently, you should only use the array form for maximum cross-version compatibility.
      diff --git a/sites/all/modules/contrib/dev/ctools/includes/action-links.theme.inc b/sites/all/modules/contrib/dev/ctools/includes/action-links.theme.inc index 3a2398a..f53f59c8 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/action-links.theme.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/action-links.theme.inc @@ -1,4 +1,5 @@ ' . $links . '
    '; -} \ No newline at end of file +} diff --git a/sites/all/modules/contrib/dev/ctools/includes/ajax.inc b/sites/all/modules/contrib/dev/ctools/includes/ajax.inc index bb17bea..4d72d0c 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/ajax.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/ajax.inc @@ -1,6 +1,10 @@ TRUE, 'attributes' => array('class' => array($type, $class), 'title' => $alt))); } +/** + * Render an icon and related text as a link. This will automatically apply an AJAX class + * to the link and add the appropriate javascript to make this happen. + * + * Note: 'html' => true so be sure any text is vetted! Chances are these kinds of buttons will + * not use user input so this is a very minor concern. + * + * @param $text + * The text that will be displayed as the link. + * @param $image + * The icon image to include in the link. + * @param $dest + * The destination of the link. + * @param $alt + * The title text of the link. + * @param $class + * Any class to apply to the link. @todo this should be a options array. + * @param $type + * A type to use, in case a different behavior should be attached. Defaults + * to ctools-use-ajax. + */ +function ctools_ajax_icon_text_button($text, $image, $dest, $alt, $class = '', $type = 'use-ajax') { + drupal_add_library('system', 'drupal.ajax'); + $rendered_image = theme('image', array('path' => $image)); + $link_content = $rendered_image . "" . $text . ""; + return l($link_content, $dest, array('html' => TRUE, 'attributes' => array('class' => array($type, $class), 'title' => $alt))); +} + /** * Set a single property to a value, on all matched elements. * @@ -57,17 +89,17 @@ function ctools_ajax_text_button($text, $dest, $alt, $class = '', $type = 'use-a * @param $name * The name or key: of the data attached to this selector. * @param $value - * The value of the data. + * The value of the data. */ function ctools_ajax_command_attr($selector, $name, $value) { ctools_add_js('ajax-responder'); return array( - 'command' => 'attr', - 'selector' => $selector, - 'name' => $name, - 'value' => $value, - ); - } + 'command' => 'attr', + 'selector' => $selector, + 'name' => $name, + 'value' => $value, + ); +} /** * Force a client-side redirect. @@ -126,4 +158,3 @@ function ctools_ajax_render_error($error = '') { print ajax_render($commands); exit; } - diff --git a/sites/all/modules/contrib/dev/ctools/includes/cache.inc b/sites/all/modules/contrib/dev/ctools/includes/cache.inc index 3918683..cace800 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/cache.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/cache.inc @@ -2,7 +2,6 @@ /** * @file - * * Plugins to handle cache-indirection. * * Simple plugin management to allow clients to more tightly control where @@ -52,7 +51,6 @@ * @param string $mechanism * A string containing the plugin name, and an optional data element to * send to the plugin separated by two colons. - * * @param string $key * The key used to identify the cache. * @@ -70,7 +68,6 @@ function ctools_cache_get($mechanism, $key) { * @param string $mechanism * A string containing the plugin name, and an optional data element to * send to the plugin separated by two colons. - * * @param string $key * The key used to identify the cache. * @@ -88,7 +85,6 @@ function ctools_cache_set($mechanism, $key, $object) { * @param string $mechanism * A string containing the plugin name, and an optional data element to * send to the plugin separated by two colons. - * * @param string $key * The key used to identify the cache. */ @@ -108,13 +104,10 @@ function ctools_cache_clear($mechanism, $key) { * @param string $mechanism * A string containing the plugin name, and an optional data element to * send to the plugin separated by two colons. - * * @param string $key * The key used to identify the cache. - * * @param string $op * The operation to call, such as 'break' or 'finalize'. - * * @param mixed $object * The cache data being operated on, in case it is necessary. This is * optional so no references should be used. @@ -150,7 +143,7 @@ function ctools_cache_operation($mechanism, $key, $op, $object = NULL) { */ function ctools_cache_find_plugin($mechanism) { if (strpos($mechanism, '::') !== FALSE) { - // use explode(2) to ensure that the data can contain double + // Use explode(2) to ensure that the data can contain double // colons, just in case. list($name, $data) = explode('::', $mechanism, 2); } diff --git a/sites/all/modules/contrib/dev/ctools/includes/cleanstring.inc b/sites/all/modules/contrib/dev/ctools/includes/cleanstring.inc index 56b3e36..11ffcef 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/cleanstring.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/cleanstring.inc @@ -1,5 +1,4 @@ $length) { - $string = drupal_substr($string, 0, $length + 1); // leave one more character - if ($last_break = strrpos($string, $separator)) { // space exists AND is not on position 0 + // Leave one more character. + $string = drupal_substr($string, 0, $length + 1); + // Space exists AND is not on position 0. + if ($last_break = strrpos($string, $separator)) { $string = substr($string, 0, $last_break); } else { diff --git a/sites/all/modules/contrib/dev/ctools/includes/collapsible.theme.inc b/sites/all/modules/contrib/dev/ctools/includes/collapsible.theme.inc index f7bbbb3..81df4bc 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/collapsible.theme.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/collapsible.theme.inc @@ -30,8 +30,8 @@ function ctools_collapsible_theme(&$items) { * Text to put in the handle/title area of the div. * @param $content * Text to put in the content area of the div, this is what will get - * collapsed - * @param $collapsed = FALSE + * collapsed. + * @param $collapsed * If true, this div will start out collapsed. */ function theme_ctools_collapsible($vars) { @@ -56,8 +56,8 @@ function theme_ctools_collapsible($vars) { * Text to put in the handle/title area of the div. * @param $content * Text to put in the content area of the div, this is what will get - * collapsed - * @param $collapsed = FALSE + * collapsed. + * @param $collapsed * If true, this div will start out collapsed. */ function theme_ctools_collapsible_remembered($vars) { @@ -76,4 +76,3 @@ function theme_ctools_collapsible_remembered($vars) { return $output; } - diff --git a/sites/all/modules/contrib/dev/ctools/includes/content.inc b/sites/all/modules/contrib/dev/ctools/includes/content.inc index 890ec23..49c3565 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/content.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/content.inc @@ -86,7 +86,7 @@ function ctools_content_process(&$plugin, $info) { /** * Fetch metadata on a specific content_type plugin. * - * @param $content type + * @param mixed $content * Name of a panel content type. * * @return @@ -146,7 +146,7 @@ function ctools_content_get_subtypes($type) { if (is_array($function)) { $subtypes = $function; } - else if (function_exists($function)) { + elseif (function_exists($function)) { // Cast to array to prevent errors from non-array returns. $subtypes = (array) $function($plugin); } @@ -155,6 +155,11 @@ function ctools_content_get_subtypes($type) { // Walk through the subtypes and ensure minimal settings are // retained. foreach ($subtypes as $id => $subtype) { + // Ensure that the 'subtype_id' value exists. + if (!isset($subtype['subtype_id'])) { + $subtypes[$id]['subtype_id'] = $id; + } + // Use exact name since this is a modify by reference. ctools_content_prepare_subtype($subtypes[$id], $plugin); } @@ -202,6 +207,13 @@ function ctools_content_get_subtype($type, $subtype_id) { } if ($subtype) { + // Ensure that the 'subtype_id' value exists. This is also done in + // ctools_content_get_subtypes(), but it wouldn't be called if the plugin + // provides the subtype through its own function. + if (!isset($subtype['subtype_id'])) { + $subtype['subtype_id'] = $subtype_id; + } + ctools_content_prepare_subtype($subtype, $plugin); } return $subtype; @@ -217,6 +229,7 @@ function ctools_content_prepare_subtype(&$subtype, $plugin) { } } + // Trigger hook_ctools_content_subtype_alter(). drupal_alter('ctools_content_subtype', $subtype, $plugin); } @@ -241,9 +254,10 @@ function ctools_content_prepare_subtype(&$subtype, $plugin) { * Any incoming content, if this display is a wrapper. * * @return - * The content as rendered by the plugin. This content should be an array - * with the following possible keys: + * The content as rendered by the plugin, or NULL. + * This content should be an object with the following possible properties: * - title: The safe to render title of the content. + * - title_heading: The title heading. * - content: The safe to render HTML content. * - links: An array of links associated with the content suitable for * theme('links'). @@ -293,21 +307,22 @@ function ctools_content_render($type, $subtype, $conf, $keywords = array(), $arg $content->subtype = $subtype; } - // Override the title if configured to + // Override the title if configured to. if (!empty($conf['override_title'])) { // Give previous title as an available substitution here. $keywords['%title'] = empty($content->title) ? '' : $content->title; $content->original_title = $keywords['%title']; $content->title = $conf['override_title_text']; + $content->title_heading = isset($conf['override_title_heading']) ? $conf['override_title_heading'] : 'h2'; } if (!empty($content->title)) { - // Perform substitutions + // Perform substitutions. if (!empty($keywords) || !empty($context)) { $content->title = ctools_context_keyword_substitute($content->title, $keywords, $context); } - // Sterilize the title + // Sterilize the title. $content->title = filter_xss_admin($content->title); // If a link is specified, populate. @@ -318,7 +333,7 @@ function ctools_content_render($type, $subtype, $conf, $keywords = array(), $arg else { $url = $content->title_link; } - // set defaults so we don't bring up notices + // Set defaults so we don't bring up notices. $url += array('href' => '', 'attributes' => array(), 'query' => array(), 'fragment' => '', 'absolute' => NULL, 'html' => TRUE); $content->title = l($content->title, $url['href'], $url); } @@ -339,7 +354,16 @@ function ctools_content_editable($type, $subtype, $conf) { return FALSE; } - if ($function = ctools_plugin_get_function($subtype, 'check editable')) { + $function = FALSE; + + if (!empty($subtype['check editable'])) { + $function = ctools_plugin_get_function($subtype, 'check editable'); + } + elseif (!empty($type['check editable'])) { + $function = ctools_plugin_get_function($type, 'check editable'); + } + + if ($function) { return $function($type, $subtype, $conf); } @@ -362,7 +386,7 @@ function ctools_content_admin_title($type, $subtype, $conf, $context = NULL) { if (is_array($type)) { $plugin = $type; } - else if (is_string($type)) { + elseif (is_string($type)) { $plugin = ctools_get_content_type($type); } else { @@ -380,10 +404,10 @@ function ctools_content_admin_title($type, $subtype, $conf, $context = NULL) { return $function($subtype, $conf, $pane_context); } - else if (isset($plugin['admin title'])) { + elseif (isset($plugin['admin title'])) { return $plugin['admin title']; } - else if (isset($plugin['title'])) { + elseif (isset($plugin['title'])) { return $plugin['title']; } } @@ -418,7 +442,7 @@ function ctools_content_get_defaults($plugin, $subtype) { if (isset($plugin['defaults'])) { $defaults = $plugin['defaults']; } - else if (isset($subtype['defaults'])) { + elseif (isset($subtype['defaults'])) { $defaults = $subtype['defaults']; } if (isset($defaults)) { @@ -427,7 +451,7 @@ function ctools_content_get_defaults($plugin, $subtype) { return $return; } } - else if (is_array($defaults)) { + elseif (is_array($defaults)) { return $defaults; } } @@ -461,7 +485,7 @@ function ctools_content_admin_info($type, $subtype, $conf, $context = NULL) { if (empty($output) || !is_object($output)) { $output = new stdClass(); - // replace the _ with " " for a better output + // Replace the _ with " " for a better output. $subtype = check_plain(str_replace("_", " ", $subtype)); $output->title = $subtype; $output->content = t('No info available.'); @@ -470,7 +494,7 @@ function ctools_content_admin_info($type, $subtype, $conf, $context = NULL) { } /** - * Add the default FAPI elements to the content type configuration form + * Add the default FAPI elements to the content type configuration form. */ function ctools_content_configure_form_defaults($form, &$form_state) { $plugin = $form_state['plugin']; @@ -504,8 +528,26 @@ function ctools_content_configure_form_defaults($form, &$form_state) { '#size' => 35, '#id' => 'override-title-textfield', '#dependency' => array('override-title-checkbox' => array(1)), - '#dependency_type' => 'disable', + '#dependency_type' => 'hidden', ); + $form['override_title_heading'] = array( + '#type' => 'select', + '#default_value' => isset($conf['override_title_heading']) ? $conf['override_title_heading'] : 'h2', + '#options' => array( + 'h1' => t('h1'), + 'h2' => t('h2'), + 'h3' => t('h3'), + 'h4' => t('h4'), + 'h5' => t('h5'), + 'h6' => t('h6'), + 'div' => t('div'), + 'span' => t('span'), + ), + '#id' => 'override-title-heading', + '#dependency' => array('override-title-checkbox' => array(1)), + '#dependency_type' => 'hidden', + ); + $form['aligner_stop'] = array( '#markup' => '
  • ', ); @@ -536,6 +578,7 @@ function ctools_content_configure_form_defaults_submit(&$form, &$form_state) { if (isset($form_state['values']['override_title'])) { $form_state['conf']['override_title'] = $form_state['values']['override_title']; $form_state['conf']['override_title_text'] = $form_state['values']['override_title_text']; + $form_state['conf']['override_title_heading'] = $form_state['values']['override_title_heading']; } } @@ -568,7 +611,7 @@ function ctools_content_form($op, $form_info, &$form_state, $plugin, $subtype_na if (!empty($subtype['add form'])) { _ctools_content_create_form_info($form_info, $subtype['add form'], $subtype, $subtype, $op); } - else if (!empty($plugin['add form'])) { + elseif (!empty($plugin['add form'])) { _ctools_content_create_form_info($form_info, $plugin['add form'], $plugin, $subtype, $op); } } @@ -578,7 +621,7 @@ function ctools_content_form($op, $form_info, &$form_state, $plugin, $subtype_na if (!empty($subtype['edit form'])) { _ctools_content_create_form_info($form_info, $subtype['edit form'], $subtype, $subtype, $op); } - else if (!empty($plugin['edit form'])) { + elseif (!empty($plugin['edit form'])) { _ctools_content_create_form_info($form_info, $plugin['edit form'], $plugin, $subtype, $op); } } @@ -597,7 +640,7 @@ function _ctools_content_create_form_info(&$form_info, $info, $plugin, $subtype, if (empty($subtype['title'])) { $title = t('Configure'); } - else if ($op == 'add') { + elseif ($op == 'add') { $title = t('Configure new !subtype_title', array('!subtype_title' => $subtype['title'])); } else { @@ -612,7 +655,7 @@ function _ctools_content_create_form_info(&$form_info, $info, $plugin, $subtype, ), ); } - else if (is_array($info)) { + elseif (is_array($info)) { $form_info['order'] = array(); $form_info['forms'] = array(); $count = 0; @@ -663,7 +706,7 @@ function ctools_content_get_available_types($contexts = NULL, $has_content = FAL foreach ($plugins as $id => $plugin) { foreach (ctools_content_get_subtypes($plugin) as $subtype_id => $subtype) { - // exclude items that require content if we're saying we don't + // Exclude items that require content if we're saying we don't // provide it. if (!empty($subtype['requires content']) && !$has_content) { continue; @@ -687,6 +730,11 @@ function ctools_content_get_available_types($contexts = NULL, $has_content = FAL } } + // Check if the content type provides an access callback. + if (isset($subtype['create content access']) && function_exists($subtype['create content access']) && !$subtype['create content access']($plugin, $subtype)) { + continue; + } + // If we made it through all the tests, then we can use this content. $available[$id][$subtype_id] = $subtype; } @@ -698,7 +746,6 @@ function ctools_content_get_available_types($contexts = NULL, $has_content = FAL * Get an array of all content types that can be fed into the * display editor for the add content list, regardless of * availability. - * */ function ctools_content_get_all_types() { $plugins = ctools_get_content_types(); diff --git a/sites/all/modules/contrib/dev/ctools/includes/content.menu.inc b/sites/all/modules/contrib/dev/ctools/includes/content.menu.inc index 93884fc..64d44b0 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/content.menu.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/content.menu.inc @@ -23,68 +23,156 @@ function ctools_content_menu(&$items) { /** * Helper function for autocompletion of entity titles. */ -function ctools_content_autocomplete_entity($type, $string = '') { - $entity = entity_get_info($type); +function ctools_content_autocomplete_entity($entity_type, $string = '') { if ($string != '') { - // @todo verify the query logic here, it's untested. - // Set up the query - $query = db_select($entity['base table'], 'b'); - if ($entity['entity keys']['label']) { - $query->fields('b', array($entity['entity keys']['id'], $entity['entity keys']['label']))->range(0, 10); - } - else { - $query->fields('b', array($entity['entity keys']['id']))->range(0, 10); + $entity_info = entity_get_info($entity_type); + + if (!module_exists('entity')) { + module_load_include('inc', 'ctools', 'includes/entity-access'); + _ctools_entity_access($entity_info, $entity_type); } + // We must query all ids, because if every one of the 10 don't have access + // the user may never be able to autocomplete a node title. $preg_matches = array(); + $matches = array(); $match = preg_match('/\[id: (\d+)\]/', $string, $preg_matches); if (!$match) { $match = preg_match('/^id: (\d+)/', $string, $preg_matches); } + // If an ID match was found, use that ID rather than the whole string. if ($match) { - $query->condition('b.' . $entity['entity keys']['id'], $preg_matches[1]); - } - elseif ($entity['entity keys']['label']) { - $query->condition('b.' . $entity['entity keys']['label'], '%' . db_like($string) . '%', 'LIKE'); - } - - $matches = array(); - if ($type == 'node') { - if (!user_access('bypass node access')) { - // If the user is able to view their own unpublished nodes, allow them - // to see these in addition to published nodes. - if (user_access('view own unpublished content')) { - $query->condition(db_or() - ->condition('b.status', NODE_PUBLISHED) - ->condition('b.uid', $GLOBALS['user']->uid) - ); - } - else { - // If not, restrict the query to published nodes. - $query->condition('b.status', NODE_PUBLISHED); - } - } - - $query->addTag('node_access'); - $query->join('users', 'u', 'b.uid = u.uid'); - $query->addField('u', 'name', 'name'); - - foreach ($query->execute() as $nodeish) { - $name = empty($nodeish->name) ? variable_get('anonymous', t('Anonymous')) : check_plain($nodeish->name); - $matches[$nodeish->title . " [id: $nodeish->nid]"] = '' . check_plain($nodeish->title) . ' (' . t('by @user', array('@user' => $name)) . ')'; - } + $entity_id = $preg_matches[1]; + $results = _ctools_getReferencableEntities($entity_type, $entity_info, $entity_id, '=', 1); } else { - foreach ($query->execute() as $item) { - $id = $item->{$entity['entity keys']['id']}; - if ($entity['entity keys']['label']) { - $matches[$item->{$entity['entity keys']['label']} . " [id: $id]"] = '' . check_plain($item->{$entity['entity keys']['label']}) . ''; - } - else { - $matches["[id: $id]"] = '' . check_plain($item->{$entity['entity keys']['id']}) . ''; - } + // We cannot find results if the entity doesn't have a label to search. + if (!isset($entity_info['entity keys']['label'])) { + drupal_json_output(array("[id: NULL]" => '' . t('Entity Type !entity_type does not support autocomplete search.', array('!entity_type' => $entity_type)) . '')); + return; } + $results = _ctools_getReferencableEntities($entity_type, $entity_info, $string, 'LIKE', 10); } + foreach ($results as $entity_id => $result) { + $matches[$result['label'] . " [id: $entity_id]"] = '' . check_plain($result['label']) . ''; + $matches[$result['label'] . " [id: $entity_id]"] .= isset($result['bundle']) ? ' (' . check_plain($result['bundle']) . ')' : ''; + } + drupal_json_output($matches); } } + +/** + * Use EntityReference_SelectionHandler_Generic class to build our search query. + */ +function _ctools_buildQuery($entity_type, $entity_info, $match = NULL, $match_operator = 'CONTAINS') { + $base_table = $entity_info['base table']; + $label_key = $entity_info['entity keys']['label']; + $query = db_select($base_table) + ->fields($base_table, array($entity_info['entity keys']['id'])); + + if (isset($match)) { + if (isset($label_key)) { + $query->condition($base_table . '.' . $label_key, '%' . $match . '%', $match_operator); + } + // This should never happen, but double check just in case. + else { + return array(); + } + } + // Add a generic entity access tag to the query. + $query->addTag('ctools'); + + // We have to perform two checks. First check is a query alter (with tags) + // in an attempt to only return results that have access. However, this is + // not full-proof since entities many not implement hook_access query tag. + // This is why we have a second check after entity load, before we display + // the label of an entity. + if ($entity_type == 'comment') { + // Adding the 'comment_access' tag is sadly insufficient for comments: core
 + // requires us to also know about the concept of 'published' and
 + // 'unpublished'. + if (!user_access('administer comments')) { + $query->condition('comment.status', COMMENT_PUBLISHED); + } + + // Join to a node if the user does not have node access bypass permissions
 + // to obey node published permissions. + if (!user_access('bypass node access')) { + $node_alias = $query->innerJoin('node', 'n', '%alias.nid = comment.nid'); + $query->condition($node_alias . '.status', NODE_PUBLISHED); + } + $query->addTag('node_access'); + } + else { + $query->addTag($entity_type . '_access'); + } + + // Add the sort option. + if (isset($label_key)) { + $query->orderBy($base_table . '.' . $label_key, 'ASC'); + } + + return $query; +} + +/** + * Private function to get referencable entities. Based on code from the + * Entity Reference module. + */ +function _ctools_getReferencableEntities($entity_type, $entity_info, $match = NULL, $match_operator = 'LIKE', $limit = 0) { + global $user; + $account = $user; + $options = array(); + // We're an entity ID, return the id. + if (is_numeric($match) && $match_operator == '=') { + if ($entity = array_shift(entity_load($entity_type, array($match)))) { + if (isset($entity_info['access callback']) && function_exists($entity_info['access callback'])) { + if ($entity_info['access callback']('view', $entity, $account, $entity_type)) { + $label = entity_label($entity_type, $entity); + return array( + $match => array( + 'label' => !empty($label) ? $label : $entity->{$entity_info['entity keys']['id']}, + 'bundle' => !empty($entity_info['entity keys']['bundle']) ? check_plain($entity->{$entity_info['entity keys']['bundle']}) : NULL, + ), + ); + } + } + } + // If you don't have access, or an access callback or a valid entity, just + // Return back the Entity ID. + return array( + $match => array( + 'label' => $match, + 'bundle' => NULL, + ), + ); + } + + // We have matches, build a query to fetch the result. + if ($query = _ctools_buildQuery($entity_type, $entity_info, $match, $match_operator)) { + if ($limit > 0) { + $query->range(0, $limit); + } + + $results = $query->execute(); + + if (!empty($results)) { + foreach ($results as $record) { + $entities = entity_load($entity_type, array($record->{$entity_info['entity keys']['id']})); + $entity = array_shift($entities); + if (isset($entity_info['access callback']) && function_exists($entity_info['access callback'])) { + if ($entity_info['access callback']('view', $entity, $account, $entity_type)) { + $label = entity_label($entity_type, $entity); + $options[$record->{$entity_info['entity keys']['id']}] = array( + 'label' => !empty($label) ? $label : $entity->{$entity_info['entity keys']['id']}, + 'bundle' => !empty($entity_info['entity keys']['bundle']) ? check_plain($entity->{$entity_info['entity keys']['bundle']}) : NULL, + ); + } + } + } + } + return $options; + } + return array(); +} diff --git a/sites/all/modules/contrib/dev/ctools/includes/content.plugin-type.inc b/sites/all/modules/contrib/dev/ctools/includes/content.plugin-type.inc index a0debc3..0364a92 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/content.plugin-type.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/content.plugin-type.inc @@ -14,4 +14,4 @@ function ctools_content_plugin_type(&$items) { 'path' => drupal_get_path('module', 'ctools') . '/includes', ), ); -} \ No newline at end of file +} diff --git a/sites/all/modules/contrib/dev/ctools/includes/context-access-admin.inc b/sites/all/modules/contrib/dev/ctools/includes/context-access-admin.inc index 76643cf..d8c4f3b 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/context-access-admin.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/context-access-admin.inc @@ -245,10 +245,10 @@ function ctools_access_admin_form_submit($form, &$form_state) { // -------------------------------------------------------------------------- // AJAX menu entry points. - /** * AJAX callback to add a new access test to the list. */ + function ctools_access_ajax_add($fragment = NULL, $name = NULL) { ctools_include('ajax'); ctools_include('modal'); @@ -263,7 +263,7 @@ function ctools_access_ajax_add($fragment = NULL, $name = NULL) { ctools_ajax_render_error(); } - // Separate the fragment into 'module' and 'argument' + // Separate the fragment into 'module' and 'argument'. if (strpos($fragment, '-') === FALSE) { $module = $fragment; $argument = NULL; @@ -279,7 +279,7 @@ function ctools_access_ajax_add($fragment = NULL, $name = NULL) { list($access, $contexts) = $function($argument); - // Make sure we have the logged in user context + // Make sure we have the logged in user context. if (!isset($contexts['logged-in-user'])) { $contexts['logged-in-user'] = ctools_access_get_loggedin_context(); } @@ -306,6 +306,9 @@ function ctools_access_ajax_add($fragment = NULL, $name = NULL) { ); $output = ctools_modal_form_wrapper('ctools_access_ajax_edit_item', $form_state); + $access = $form_state['access']; + $access['plugins'][$id] = $form_state['test']; + if (!isset($output[0])) { $function = $module . '_ctools_access_set'; if (function_exists($function)) { @@ -333,7 +336,7 @@ function ctools_access_ajax_edit($fragment = NULL, $id = NULL) { ctools_ajax_render_error(); } - // Separate the fragment into 'module' and 'argument' + // Separate the fragment into 'module' and 'argument'. if (strpos($fragment, '-') === FALSE) { $module = $fragment; $argument = NULL; @@ -353,7 +356,7 @@ function ctools_access_ajax_edit($fragment = NULL, $id = NULL) { ctools_ajax_render_error(); } - // Make sure we have the logged in user context + // Make sure we have the logged in user context. if (!isset($contexts['logged-in-user'])) { $contexts['logged-in-user'] = ctools_access_get_loggedin_context(); } @@ -367,12 +370,14 @@ function ctools_access_ajax_edit($fragment = NULL, $id = NULL) { 'contexts' => $contexts, 'title' => t('Edit criteria'), 'ajax' => TRUE, - 'ajax' => TRUE, 'modal' => TRUE, 'modal return' => TRUE, ); $output = ctools_modal_form_wrapper('ctools_access_ajax_edit_item', $form_state); + $access = $form_state['access']; + $access['plugins'][$id] = $form_state['test']; + if (!isset($output[0])) { $function = $module . '_ctools_access_set'; if (function_exists($function)) { @@ -452,7 +457,7 @@ function ctools_access_ajax_delete($fragment = NULL, $id = NULL) { ajax_render_error(); } - // Separate the fragment into 'module' and 'argument' + // Separate the fragment into 'module' and 'argument'. if (strpos($fragment, '-') === FALSE) { $module = $fragment; $argument = NULL; @@ -472,7 +477,7 @@ function ctools_access_ajax_delete($fragment = NULL, $id = NULL) { unset($access['plugins'][$id]); } - // re-cache + // re-cache. $function = $module . '_ctools_access_set'; if (function_exists($function)) { $function($argument, $access); diff --git a/sites/all/modules/contrib/dev/ctools/includes/context-admin.inc b/sites/all/modules/contrib/dev/ctools/includes/context-admin.inc index 7e5cd02..533c89f 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/context-admin.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/context-admin.inc @@ -376,11 +376,11 @@ function ctools_context_ajax_item_add($mechanism = NULL, $type = NULL, $cache_ke 'path' => "ctools/context/ajax/add/$mechanism/$type/$cache_key/$name/%step", 'show cancel' => TRUE, 'default form' => 'ctools_edit_context_form_defaults', - 'auto caching' => TRUE, + 'auto cache' => TRUE, 'cache mechanism' => $mechanism, 'cache key' => $cache_key, // This is stating what the cache will be referred to in $form_state - 'cache storage' => 'object', + 'cache location' => 'object', ); if ($type == 'requiredcontext') { @@ -395,7 +395,7 @@ function ctools_context_ajax_item_add($mechanism = NULL, $type = NULL, $cache_ke if (!empty($form_state['cancel'])) { $output = array(ctools_modal_command_dismiss()); } - else if (!empty($form_state['complete'])) { + elseif (!empty($form_state['complete'])) { // Successful submit -- move temporary data to location. // Create a reference to the place our context lives. Since this is fairly @@ -416,12 +416,20 @@ function ctools_context_ajax_item_add($mechanism = NULL, $type = NULL, $cache_ke ctools_cache_operation($mechanism, $cache_key, 'finalize', $object); // Very irritating way to update the form for our contexts. - $arg_form_state = array('values' => array()); + $arg_form_state = form_state_defaults() + array( + 'values' => array(), + 'process_input' => FALSE, + 'complete form' => array(), + ); + + $rel_form_state = $arg_form_state; $arg_form = array( '#post' => array(), '#programmed' => FALSE, '#tree' => FALSE, + '#parents' => array(), + '#array_parents' => array(), ); // Build a chunk of the form to merge into the displayed form @@ -446,6 +454,8 @@ function ctools_context_ajax_item_add($mechanism = NULL, $type = NULL, $cache_ke '#post' => array(), '#programmed' => FALSE, '#tree' => FALSE, + '#parents' => array(), + '#array_parents' => array(), ); $rel_form['relationship'] = array( @@ -460,7 +470,7 @@ function ctools_context_ajax_item_add($mechanism = NULL, $type = NULL, $cache_ke $output = array(); if (!empty($available_relationships)) { ctools_context_add_item_table_buttons('relationship', $mechanism, $rel_form, $available_relationships); - $rel_form = form_builder('dummy_form_id', $rel_form, $arg_form_state); + $rel_form = form_builder('dummy_form_id', $rel_form, $rel_form_state); $output[] = ajax_command_replace('div#ctools-relationships-table div.buttons', drupal_render($rel_form)); } @@ -551,11 +561,11 @@ function ctools_context_ajax_item_edit($mechanism = NULL, $type = NULL, $cache_k 'path' => "ctools/context/ajax/configure/$mechanism/$type/$cache_key/$position/%step", 'show cancel' => TRUE, 'default form' => 'ctools_edit_context_form_defaults', - 'auto caching' => TRUE, + 'auto cache' => TRUE, 'cache mechanism' => $mechanism, 'cache key' => $cache_key, // This is stating what the cache will be referred to in $form_state - 'cache storage' => 'object', + 'cache location' => 'object', ); if ($type == 'requiredcontext') { @@ -570,7 +580,7 @@ function ctools_context_ajax_item_edit($mechanism = NULL, $type = NULL, $cache_k if (!empty($form_state['cancel'])) { $output = array(ctools_modal_command_dismiss()); } - else if (!empty($form_state['complete'])) { + elseif (!empty($form_state['complete'])) { // successful submit $ref[$position] = $conf; if (isset($object->temporary)) { @@ -582,8 +592,16 @@ function ctools_context_ajax_item_edit($mechanism = NULL, $type = NULL, $cache_k $output = array(); $output[] = ctools_modal_command_dismiss(); + $arg_form_state = form_state_defaults() + array( + 'values' => array(), + 'process_input' => FALSE, + 'complete form' => array(), + ); + $arg_form = array( '#post' => array(), + '#parents' => array(), + '#array_parents' => array(), '#programmed' => FALSE, '#tree' => FALSE, ); @@ -639,7 +657,7 @@ function ctools_context_get_defaults($plugin_definition, $object, $type) { if (isset($plugin_definition['defaults'])) { $defaults = $plugin_definition['defaults']; } - else if (isset($subtype['defaults'])) { + elseif (isset($subtype['defaults'])) { $defaults = $subtype['defaults']; } @@ -649,7 +667,7 @@ function ctools_context_get_defaults($plugin_definition, $object, $type) { $conf += $settings; } } - else if (is_array($defaults)) { + elseif (is_array($defaults)) { $conf += $defaults; } } @@ -718,6 +736,15 @@ function ctools_edit_context_form_defaults($form, &$form_state) { '#default_value' => $conf['keyword'], ); + if ($type_info['key'] == 'requiredcontexts') { + $form['optional'] = array( + '#type' => 'checkbox', + '#title' => t('Context is optional'), + '#default_value' => !empty($form_state['conf']['optional']), + '#description' => t('This context need not be present for the component to function.'), + ); + } + $form['#submit'][] = 'ctools_edit_context_form_defaults_submit'; return $form; @@ -734,6 +761,9 @@ function ctools_edit_context_form_defaults_submit(&$form, &$form_state) { $form_state['conf']['default'] = $form_state['values']['default']; $form_state['conf']['title'] = $form_state['values']['title']; } + if ($form_state['type info']['key'] == 'requiredcontexts') { + $form_state['conf']['optional'] = $form_state['values']['optional']; + } $form_state['conf']['identifier'] = $form_state['values']['identifier']; $form_state['conf']['keyword'] = $form_state['values']['keyword']; diff --git a/sites/all/modules/contrib/dev/ctools/includes/context-task-handler.inc b/sites/all/modules/contrib/dev/ctools/includes/context-task-handler.inc index 37aa6df..ed4acad 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/context-task-handler.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/context-task-handler.inc @@ -4,9 +4,9 @@ * @file * Support for creating 'context' type task handlers. * - * Context task handlers expect the task to provide 0 or more contexts. The - * task handler should use those contexts as selection rules, as well as - * rendering with them. + * Context task handlers expect the task to provide 0 or more contexts. The task + * handler should use those contexts as selection rules, as well as rendering + * with them. * * The functions and forms in this file should be common to every context type * task handler made. @@ -31,6 +31,7 @@ * If TRUE then this renderer owns the page and can use theme('page') * for no blocks; if false, output is returned regardless of any no * blocks settings. + * * @return * Either the output or NULL if there was output, FALSE if no handler * accepted the task. If $page is FALSE then the $info block is returned instead. @@ -84,7 +85,7 @@ function ctools_context_handler_get_render_handler($task, $subtask, $handlers, $ */ function ctools_context_handler_default_test($handler, $base_contexts, $args) { ctools_include('context'); - // Add my contexts + // Add my contexts. $contexts = ctools_context_handler_get_handler_contexts($base_contexts, $handler); // Test. @@ -128,7 +129,7 @@ function ctools_context_handler_render_handler($task, $subtask, $handler, $conte 'contexts' => $contexts, 'task' => $task, 'subtask' => $subtask, - 'handler' => $handler + 'handler' => $handler, ); drupal_alter('ctools_render', $info, $page, $context); @@ -141,12 +142,15 @@ function ctools_context_handler_render_handler($task, $subtask, $handler, $conte switch ($info['response code']) { case 403: return MENU_ACCESS_DENIED; + case 404: return MENU_NOT_FOUND; + case 410: drupal_add_http_header('Status', '410 Gone'); drupal_exit(); break; + case 301: case 302: case 303: @@ -161,8 +165,8 @@ function ctools_context_handler_render_handler($task, $subtask, $handler, $conte 'query' => $info['query'], 'fragment' => $info['fragment'], ); - return drupal_goto($info['destination'], $options, $info['response code']); - // @todo -- should other response codes be supported here? + drupal_goto($info['destination'], $options, $info['response code']); + // @todo -- should other response codes be supported here? } } @@ -222,7 +226,7 @@ function ctools_context_handler_render_handler($task, $subtask, $handler, $conte } /** - * Default function to provide contextual link for a task as defined by the handler. + * Provides contextual link for a task as defined by the handler. * * This provides a simple link to th main content operation and is suitable * for most normal handlers. Setting 'contextual link' to a function overrides @@ -241,7 +245,7 @@ function ctools_task_handler_default_contextual_link($handler, $plugin, $context if (is_array($plugin['tab operation'])) { $trail = $plugin['tab operation']; } - else if (function_exists($plugin['tab operation'])) { + elseif (function_exists($plugin['tab operation'])) { $trail = $plugin['tab operation']($handler, $contexts, $args); } } @@ -251,7 +255,8 @@ function ctools_task_handler_default_contextual_link($handler, $plugin, $context 'href' => $path, 'title' => $title, 'query' => drupal_get_destination(), - )); + ), + ); return $links; } @@ -259,17 +264,22 @@ function ctools_task_handler_default_contextual_link($handler, $plugin, $context /** * Called to execute actions that should happen before a handler is rendered. */ -function ctools_context_handler_pre_render($handler, $contexts, $args) { } +function ctools_context_handler_pre_render($handler, $contexts, $args) { + foreach (module_implements('ctools_context_handler_pre_render') as $module) { + $function = $module . '_ctools_context_handler_pre_render'; + $function($handler, $contexts, $args); + } +} /** * Compare arguments to contexts for selection purposes. * - * @param $handler + * @param object $handler * The handler in question. - * @param $contexts + * @param object $contexts * The context objects provided by the task. * - * @return + * @return bool * TRUE if these contexts match the selection rules. NULL or FALSE * otherwise. */ @@ -288,12 +298,15 @@ function ctools_context_handler_select($handler, $contexts) { * These summary strings are used to communicate to the user what * arguments the task handlers are selecting. * - * @param $task + * @param object $task * The loaded task plugin. - * @param $subtask + * @param object $subtask * The subtask id. - * @param $handler + * @param object $handler * The handler to be checked. + * + * @return array + * Returns array of summary strings for arguments selected by task handlers. */ function ctools_context_handler_summary($task, $subtask, $handler) { if (empty($handler->conf['access']['plugins'])) { @@ -320,7 +333,6 @@ function ctools_context_handler_summary($task, $subtask, $handler) { // the task handler, for example) but sometimes we need them separately // (when a task has contexts loaded and is trying out the task handlers, // for example). Therefore there are two paths we can take to getting contexts. - /** * Load the contexts for a task, using arguments. * @@ -368,7 +380,7 @@ function ctools_context_handler_get_all_contexts($task, $subtask, $handler) { * expects things in a certain, kind of clunky format. */ function ctools_context_handler_get_handler_object($handler) { - $object = new stdClass; + $object = new stdClass(); $object->name = $handler->name; $object->contexts = isset($handler->conf['contexts']) ? $handler->conf['contexts'] : array(); $object->relationships = isset($handler->conf['relationships']) ? $handler->conf['relationships'] : array(); @@ -382,7 +394,7 @@ function ctools_context_handler_get_handler_object($handler) { * arguments from the task. */ function ctools_context_handler_get_task_object($task, $subtask, $handler) { - $object = new stdClass; + $object = new stdClass(); $object->name = !empty($handler->name) ? $handler->name : 'temp'; $object->base_contexts = ctools_context_handler_get_base_contexts($task, $subtask, TRUE); $object->arguments = ctools_context_handler_get_task_arguments($task, $subtask); @@ -456,7 +468,7 @@ function ctools_context_handler_edit_criteria($form, &$form_state) { ctools_modal_add_plugin_js(ctools_get_access_plugins()); ctools_include('context-access-admin'); $form_state['module'] = (isset($form_state['module'])) ? $form_state['module'] : 'page_manager_task_handler'; - // Encode a bunch of info into the argument so we can get our cache later + // Encode a bunch of info into the argument so we can get our cache later. $form_state['callback argument'] = $form_state['task_name'] . '*' . $form_state['handler']->name; $form_state['access'] = $form_state['handler']->conf['access']; $form_state['no buttons'] = TRUE; @@ -472,7 +484,7 @@ function ctools_context_handler_edit_criteria($form, &$form_state) { } /** - * Submit handler for rules selection + * Submit handler for rules selection. */ function ctools_context_handler_edit_criteria_submit(&$form, &$form_state) { $form_state['handler']->conf['access']['logic'] = $form_state['values']['logic']; @@ -537,4 +549,3 @@ function ctools_context_handler_edit_context_submit(&$form, &$form_state) { unset($form_state['page']->context_cache[$cache_name]); } } - diff --git a/sites/all/modules/contrib/dev/ctools/includes/context.inc b/sites/all/modules/contrib/dev/ctools/includes/context.inc index 93be748..0fc6208 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/context.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/context.inc @@ -2,7 +2,6 @@ /** * @file - * * Contains code related to the ctools system of 'context'. * * Context, originally from Panels, is a method of packaging objects into @@ -28,28 +27,104 @@ * of the context is important. */ class ctools_context { - var $type = NULL; - var $data = NULL; - // The title of this object. - var $title = ''; - // The title of the page if this object exists - var $page_title = ''; - // The identifier (in the UI) of this object - var $identifier = ''; - var $argument = NULL; - var $keyword = ''; - var $original_argument = NULL; - var $restrictions = array(); - var $empty = FALSE; + /** + * @var string|array + * A string naming this specific context type. The values 'any' and 'none' + * are special: + * - 'any': used in is_type() to match any other type. + * - 'none': used to signal the type is not defined. + */ + public $type; - function ctools_context($type = 'none', $data = NULL) { - $this->type = $type; - $this->data = $data; + /** + * @var mixed + * The data payload for this context object. + */ + public $data; + + /** + * @var string + * The title of this object. + */ + public $title; + + /** + * @var string + * The title of the page if this object exists + */ + public $page_title; + + /** + * @var string + * The identifier (in the UI) of this object. + */ + public $identifier; + + /** + * @var + */ + public $argument; + + /** + * @var string + */ + public $keyword; + + /** + * @var + */ + public $original_argument; + + /** + * @var array + */ + public $restrictions; + + /** + * @var bool + */ + public $empty; + + /** + * The ctools_context constructor. + * + * @param string $type + * The type name of this context. Should be unique. Use the machine_name + * conventions: lowercase, short, underscores and no spaces. + * @param mixed $data + * The data payload, if required for this context. + */ + public function __construct($type = 'none', $data = NULL) { + $this->type = $type; + $this->data = $data; $this->title = t('Unknown context'); + $this->page_title = ''; + $this->identifier = ''; + $this->keyword = ''; + $this->restrictions = array(); + $this->empty = FALSE; + // Other vars are NULL. } - function is_type($type) { - if ($type == 'any' || $this->type == 'any') { + /** + * Determine whether this object is of type @var $type . + * + * Both the internal value ($this->type) and the supplied value ($type) can + * be a string or an array of strings, and if one or both are arrays the match + * succeeds if at least one common element is found. + * + * Type names + * + * @param string|array $type + * 'type' can be: + * - 'any' to match all types (this is true of the internal value too). + * - an array of type name strings, when more than one type is acceptable. + * + * @return bool + * True if the type matches, False otherwise. + */ + public function is_type($type) { + if ($type === 'any' || $this->type === 'any') { return TRUE; } @@ -58,32 +133,71 @@ class ctools_context { return (bool) array_intersect($a, $b); } - function get_argument() { + /** + * Return the argument. + * + * @return mixed + * The value of $argument. + */ + public function get_argument() { return $this->argument; } - function get_original_argument() { + /** + * Return the value of argument (or arg) variable as it was passed in. + * + * For example see ctools_plugin_load_function() and ctools_terms_context(). + * + * @return mixed + * The original arg value. + */ + public function get_original_argument() { if (!is_null($this->original_argument)) { return $this->original_argument; } return $this->argument; } - function get_keyword() { + /** + * Return the keyword. + * + * @return mixed + * The value of $keyword. + */ + public function get_keyword() { return $this->keyword; } - function get_identifier() { + /** + * Return the identifier. + * + * @return mixed + * The value of $identifier. + */ + public function get_identifier() { return $this->identifier; } - function get_title() { + /** + * Return the title. + * + * @return mixed + * The value of $title. + */ + public function get_title() { return $this->title; } - function get_page_title() { + /** + * Return the page title. + * + * @return mixed + * The value of $page_title. + */ + public function get_page_title() { return $this->page_title; } + } /** @@ -91,39 +205,57 @@ class ctools_context { * match a required context type. */ class ctools_context_required { - var $keywords = ''; + /** + * @var array + * Keyword strings associated with the context. + */ + public $keywords; /** * If set, the title will be used in the selector to identify * the context. This is very useful when multiple contexts * are required to inform the user will be used for what. */ - var $title = NULL; + public $title; /** * Test to see if this context is required. */ - var $required = TRUE; + public $required = TRUE; /** * If TRUE, skip the check in ctools_context_required::select() * for contexts whose names may have changed. */ - var $skip_name_check = FALSE; + public $skip_name_check = FALSE; /** + * The ctools_context_required constructor. * - * @param $title - * The first parameter should be the 'title' of the context for use - * in UYI selectors when multiple contexts qualify. - * @param ... + * Note: Constructor accepts a variable number of arguments, with optional + * type-dependent args at the end of the list and one required argument, + * the title. Note in particular that skip_name_check MUST be passed in as + * a boolean (and not, for example, as an integer). + * + * @param string $title + * The title of the context for use in UI selectors when multiple contexts + * qualify. + * @param string $keywords * One or more keywords to use for matching which contexts are allowed. + * @param array $restrictions + * Array of context restrictions. + * @param bool $skip_name_check + * If True, skip the check in select() for contexts whose names may have + * changed. */ - function ctools_context_required($title) { + public function __construct($title) { + // If it was possible, using variadic syntax this should be: + // __construct($title, string ...$keywords, array $restrictions = NULL, bool $skip = NULL) + // but that form isn't allowed. $args = func_get_args(); $this->title = array_shift($args); - // If we have a boolean value at the end for $skip_name_check, store it + // If we have a boolean value at the end for $skip_name_check, store it. if (is_bool(end($args))) { $this->skip_name_check = array_pop($args); } @@ -133,20 +265,44 @@ class ctools_context_required { $this->restrictions = array_pop($args); } - if (count($args) == 1) { + if (count($args) === 1) { $args = array_shift($args); } $this->keywords = $args; } - function filter($contexts) { + /** + * Filter the contexts to determine which apply in the current environment. + * + * A context passes the filter if: + * - the context matches 'type' of the required keywords (uses + * ctools_context::is_type(), so includes 'any' matches, etc). + * - AND if restrictions are present, there are some common elements between + * the requirement and the context. + * + * @param array $contexts + * An array of ctools_context objects (or something which will cast to an + * array of them). The contexts to apply the filter on. + * + * @return array + * An array of context objects, keyed with the same keys used for $contexts, + * which pass the filter. + * + * @see ctools_context::is_type() + */ + public function filter($contexts) { $result = array(); - // See which of these contexts are valid + /** + * See which of these contexts are valid. + * @var ctools_context $context + */ foreach ((array) $contexts as $cid => $context) { if ($context->is_type($this->keywords)) { + // Compare to see if our contexts were met. if (!empty($this->restrictions) && !empty($context->restrictions)) { + foreach ($this->restrictions as $key => $values) { // If we have a restriction, the context must either not have that // restriction listed, which means we simply don't know what it is, @@ -155,11 +311,16 @@ class ctools_context_required { if (!is_array($values)) { $values = array($values); } - if (!empty($context->restrictions[$key]) && !array_intersect($values, $context->restrictions[$key])) { + + if (!empty($context->restrictions[$key]) + && !array_intersect($values, $context->restrictions[$key]) + ) { + // Break out to check next context; this one fails the filter. continue 2; } } } + // This context passes the filter. $result[$cid] = $context; } } @@ -167,16 +328,43 @@ class ctools_context_required { return $result; } - function select($contexts, $context) { + /** + * Select one context from the list of contexts, accounting for changed IDs. + * + * Fundamentally, this returns $contexts[$context] or FALSE if that does not + * exist. Additional logic accounts for changes in context names and dealing + * with a $contexts parameter that is not an array. + * + * If we had requested a $context but that $context doesn't exist in our + * context list, there is a good chance that what happened is the context + * IDs changed. Look for another context that satisfies our requirements, + * unless $skip_name_check is set. + * + * @param ctools_context|array $contexts + * A context, or an array of ctools_context. + * @param string $context + * A context ID. + * + * @return bool|ctools_context + * The matching ctools_context, or False if no such context was found. + */ + public function select($contexts, $context) { + // Easier to deal with a standalone object as a 1-element array of objects. if (!is_array($contexts)) { - $contexts = array($contexts); + if (is_object($contexts) && $contexts instanceof ctools_context) { + $contexts = array($contexts->id => $contexts); + } + else { + $contexts = array($contexts); + } } - // If we had requested a $context but that $context doesn't exist - // in our context list, there is a good chance that what happened - // is our context IDs changed. See if there's another context - // that satisfies our requirements. - if (!$this->skip_name_check && !empty($context) && !isset($contexts[$context])) { + // If we had requested a $context but that $context doesn't exist in our + // context list, there is a good chance that what happened is the context + // IDs changed. Check for another context that satisfies our requirements. + if (!$this->skip_name_check + && !empty($context) && !isset($contexts[$context]) + ) { $choices = $this->filter($contexts); // If we got a hit, take the first one that matches. @@ -191,6 +379,7 @@ class ctools_context_required { } return $contexts[$context]; } + } /** @@ -198,28 +387,73 @@ class ctools_context_required { * can produce empty contexts to use as placeholders. */ class ctools_context_optional extends ctools_context_required { - var $required = FALSE; - function ctools_context_optional() { - $args = func_get_args(); - call_user_func_array(array($this, 'ctools_context_required'), $args); + + /** + * {@inheritdoc} + */ + public $required = FALSE; + + /** + * Add the 'empty' context to the existing set. + * + * @param array &$contexts + * An array of ctools_context objects. + */ + public function add_empty(&$contexts) { + $context = new ctools_context('any'); + $context->title = t('No context'); + $context->identifier = t('No context'); + $contexts['empty'] = $context; } /** - * Add the 'empty' context which is possible for optional + * Filter the contexts to determine which apply in the current environment. + * + * As for ctools_context_required, but we add the empty context to those + * passed in so the check is optional (i.e. if nothing else matches, the + * empty context will, and so there will always be at least one matched). + * + * @param array $contexts + * An array of ctools_context objects (or something which will cast to an + * array of them). The contexts to apply the filter on. + * + * @return array + * An array of context objects, keyed with the same keys used for $contexts, + * which pass the filter. + * + * @see ctools_context::is_type() */ - function add_empty(&$contexts) { - $context = new ctools_context('any'); - $context->title = t('No context'); - $context->identifier = t('No context'); - $contexts = array_merge(array('empty' => $context), $contexts); - } - - function filter($contexts) { + public function filter($contexts) { + /** + * @todo We are assuming here that $contexts is actually an array, whereas + * ctools_context_required::filter only requires $contexts is convertible + * to an array. + */ $this->add_empty($contexts); return parent::filter($contexts); } - function select($contexts, $context) { + /** + * Select and return one context from the list of applicable contexts. + * + * Fundamentally, this returns $contexts[$context] or the empty context if + * that does not exist. + * + * @param array $contexts + * The applicable contexts to check. + * @param string $context + * The context id to check for. + * + * @return bool|ctools_context + * The matching ctools_context, or False if no such context was found. + * + * @see ctools_context_required::select() + */ + public function select($contexts, $context) { + /** + * @todo We are assuming here that $contexts is actually an array, whereas + * ctools_context_required::select permits ctools_context objects as well. + */ $this->add_empty($contexts); if (empty($context)) { return $contexts['empty']; @@ -229,11 +463,12 @@ class ctools_context_optional extends ctools_context_required { // Don't flip out if it can't find the context; this is optional, put // in an empty. - if ($result == FALSE) { + if ($result === FALSE) { $result = $contexts['empty']; } return $result; } + } /** @@ -248,20 +483,20 @@ class ctools_context_optional extends ctools_context_required { * Since multiple contexts can be required, this function will accept either * an array of all required contexts, or just a single required context object. * - * @param $contexts + * @param array $contexts * A keyed array of all available contexts. - * @param $required - * A ctools_context_required or ctools_context_optional object, or an array - * of such objects. + * @param array|ctools_context_required|ctools_context_optional $required + * A *_required or *_optional object, or an array of such objects, which + * define the selection condition. * - * @return + * @return array * A keyed array of contexts that match the filter. */ function ctools_context_filter($contexts, $required) { if (is_array($required)) { $result = array(); - foreach ($required as $r) { - $result = array_merge($result, _ctools_context_filter($contexts, $r)); + foreach ($required as $item) { + $result = array_merge($result, _ctools_context_filter($contexts, $item)); } return $result; } @@ -269,6 +504,21 @@ function ctools_context_filter($contexts, $required) { return _ctools_context_filter($contexts, $required); } +/** + * Helper function for ctools_context_filter(). + * + * Used to transform the required context during the merge into the final array. + * + * @internal This function DOES NOT form part of the CTools API. + * + * @param array $contexts + * A keyed array of all available contexts. + * @param ctools_context_required|ctools_context_optional $required + * A ctools_context_required or ctools_context_optional object, although if + * given something else will return an empty array. + * + * @return array + */ function _ctools_context_filter($contexts, $required) { $result = array(); @@ -288,12 +538,17 @@ function _ctools_context_filter($contexts, $required) { * If an array of required contexts is provided, one selector will be * provided for each context. * - * @param $contexts + * @param array $contexts * A keyed array of all available contexts. - * @param $required + * @param array|ctools_context_required|ctools_context_optional $required * The required context object or array of objects. + * @param array|string $default + * The default value for the select object, suitable for a #default_value + * render key. Where $required is an array, this is an array keyed by the + * same key values as $required for all keys where an empty string is not a + * suitable default. Otherwise it is just the default value. * - * @return + * @return array * A form element, or NULL if there are no contexts that satisfy the * requirements. */ @@ -301,8 +556,10 @@ function ctools_context_selector($contexts, $required, $default) { if (is_array($required)) { $result = array('#tree' => TRUE); $count = 1; - foreach ($required as $id => $r) { - $result[] = _ctools_context_selector($contexts, $r, isset($default[$id]) ? $default[$id] : '', $count++); + foreach ($required as $id => $item) { + $result[] = _ctools_context_selector( + $contexts, $item, isset($default[$id]) ? $default[$id] : '', $count++ + ); } return $result; } @@ -310,6 +567,27 @@ function ctools_context_selector($contexts, $required, $default) { return _ctools_context_selector($contexts, $required, $default); } +/** + * Helper function for ctools_context_selector(). + * + * @internal This function DOES NOT form part of the CTools API. Use the API + * function ctools_context_selector() instead. + * + * @param array $contexts + * A keyed array of all available contexts. + * @param ctools_context_required|ctools_context_optional $required + * The required context object. + * @param $default + * The default value for the select object, suitable for a #default_value + * render key. + * @param int $num + * If supplied and non-zero, the title of the select form element will be + * "Context $num", otherwise it will be "Context". + * + * @return array + * A form element, or NULL if there are no contexts that satisfy the + * requirements. + */ function _ctools_context_selector($contexts, $required, $default, $num = 0) { $filtered = ctools_context_filter($contexts, $required); $count = count($filtered); @@ -328,13 +606,15 @@ function _ctools_context_selector($contexts, $required, $default, $num = 0) { $title = $num ? t('Context %count', array('%count' => $num)) : t('Context'); } - return array( + $form = array( '#type' => 'select', '#options' => $options, '#title' => $title, '#default_value' => $default, ); } + + return $form; } /** @@ -351,8 +631,8 @@ function _ctools_context_selector($contexts, $required, $default, $num = 0) { * @param $required * The required context object or array of objects. * - * @return - * TRUE if there are enough contexts, FALSE if there are not. + * @return bool + * True if there are enough contexts, otherwise False. */ function ctools_context_match_requirements($contexts, $required) { if (!is_array($required)) { @@ -385,8 +665,13 @@ function ctools_context_match_requirements($contexts, $required) { * A keyed array of all available contexts. * @param $required * The required context object or array of objects. + * @param array|string $default + * The default value for the select object, suitable for a #default_value + * render key. Where $required is an array, this is an array keyed by the + * same key values as $required for all keys where an empty string is not a + * suitable default. Otherwise it is just the default value. * - * @return + * @return array * A form element, or NULL if there are no contexts that satisfy the * requirements. */ @@ -394,8 +679,11 @@ function ctools_context_converter_selector($contexts, $required, $default) { if (is_array($required)) { $result = array('#tree' => TRUE); $count = 1; - foreach ($required as $id => $r) { - $result[] = _ctools_context_converter_selector($contexts, $r, isset($default[$id]) ? $default[$id] : '', $count++); + foreach ($required as $id => $dependency) { + $default_id = isset($default[$id]) ? $default[$id] : ''; + $result[] = _ctools_context_converter_selector( + $contexts, $dependency, $default_id, $count++ + ); } return $result; } @@ -403,17 +691,36 @@ function ctools_context_converter_selector($contexts, $required, $default) { return _ctools_context_converter_selector($contexts, $required, $default); } +/** + * Helper function for ctools_context_converter_selector(). + * + * @internal This function DOES NOT form part of the CTools API. Use the API + * function ctools_context_converter_selector() instead. + * + * @param array $contexts + * A keyed array of all available contexts. + * @param ctools_context $required + * The required context object. + * @param $default + * The default value for the select object, suitable for a #default_value + * render key. + * @param int $num + * If supplied and non-zero, the title of the select form element will be + * "Context $num", otherwise it will be "Context". + * + * @return array|null + * A form element, or NULL if there are no contexts that satisfy the + * requirements. + */ function _ctools_context_converter_selector($contexts, $required, $default, $num = 0) { $filtered = ctools_context_filter($contexts, $required); $count = count($filtered); - $form = array(); - if ($count > 1) { // If there's more than one to choose from, create a select widget. $options = array(); foreach ($filtered as $cid => $context) { - if ($context->type == 'any') { + if ($context->type === 'any') { $options[''] = t('No context'); continue; } @@ -443,10 +750,22 @@ function _ctools_context_converter_selector($contexts, $required, $default, $num '#default_value' => $default, ); } + else { + // Not enough choices to need a selector, so don't make one. + return NULL; + } } /** * Get a list of converters available for a given context. + * + * @param string $cid + * A context ID. + * @param ctools_context $context + * The context for which converters are needed. + * + * @return array + * A list of context converters. */ function ctools_context_get_converters($cid, $context) { if (empty($context->plugin)) { @@ -458,6 +777,17 @@ function ctools_context_get_converters($cid, $context) { /** * Get a list of converters available for a given context. + * + * @internal This function DOES NOT form part of the CTools API. Use the API + * function ctools_context_get_converters() instead. + * + * @param string $id + * A context ID. + * @param string $plugin_name + * The name of the context plugin. + * + * @return array + * A list of context converters. */ function _ctools_context_get_converters($id, $plugin_name) { $plugin = ctools_get_context($plugin_name); @@ -469,7 +799,7 @@ function _ctools_context_get_converters($id, $plugin_name) { if (is_array($plugin['convert list'])) { $converters = $plugin['convert list']; } - else if ($function = ctools_plugin_get_function($plugin, 'convert list')) { + elseif ($function = ctools_plugin_get_function($plugin, 'convert list')) { $converters = (array) $function($plugin); } @@ -489,7 +819,13 @@ function _ctools_context_get_converters($id, $plugin_name) { } /** - * Get a list of all contexts + converters available. + * Get a list of all contexts converters available. + * + * For all contexts returned by ctools_get_contexts(), return the converter + * for all contexts that have one. + * + * @return array + * A list of context converters, keyed by the title of the converter. */ function ctools_context_get_all_converters() { $contexts = ctools_get_contexts(); @@ -509,21 +845,24 @@ function ctools_context_get_all_converters() { /** * Let the context convert an argument based upon the converter that was given. * - * @param $context - * The context object - * @param $converter - * The converter to use, which should be a string provided by the converter list. - * @param $converter_options - * A n array of options to pass on to the generation function. For contexts + * @param ctools_context $context + * The context object. + * @param string $converter + * The type of converter to use, which should be a string provided by the + * converter list function. + * @param array $converter_options + * An array of options to pass on to the generation function. For contexts * that use token module, of particular use is 'sanitize' => FALSE which can * get raw tokens. This should ONLY be used in values that will later be * treated as unsafe user input since these values are by themselves unsafe. * It is particularly useful to get raw values from Field API. + * + * @return string|null */ function ctools_context_convert_context($context, $converter, $converter_options = array()) { // Contexts without plugins might be optional placeholders. if (empty($context->plugin)) { - return; + return NULL; } $value = $context->argument; @@ -544,22 +883,35 @@ function ctools_context_convert_context($context, $converter, $converter_options * Choose a context or contexts based upon the selection made via * ctools_context_filter. * - * @param $contexts - * A keyed array of all available contexts - * @param $required - * The required context object provided by the plugin + * @param array $contexts + * A keyed array of all available contexts. + * @param array|ctools_context_required $required + * The required context object(s) provided by the plugin. * @param $context - * The selection made using ctools_context_selector + * The selection made using ctools_context_selector(). + * + * @return ctools_context|array|false + * Returns FALSE if $required is not an object, or array of objects, or + * the value of $required->select() for the context, or an array of those (if + * passed an array in $required). */ function ctools_context_select($contexts, $required, $context) { if (is_array($required)) { + + /** + * @var array $required + * Array of required context objects. + * @var ctools_context_required $item + * A required context object. + */ $result = array(); - foreach ($required as $id => $r) { + foreach ($required as $id => $item) { + // @todo What's the difference between the following and "empty($item)" ? if (empty($required[$id])) { continue; } - if (($result[] = _ctools_context_select($contexts, $r, $context[$id])) === FALSE) { + if (($result[] = _ctools_context_select($contexts, $item, $context[$id])) === FALSE) { return FALSE; } } @@ -569,6 +921,22 @@ function ctools_context_select($contexts, $required, $context) { return _ctools_context_select($contexts, $required, $context); } +/** + * Helper function for calling the required context object's selection function. + * + * This function DOES NOT form part of the CTools API. + * + * @param array $contexts + * A keyed array of all available contexts. + * @param ctools_context_required $required + * The required context object provided by the plugin. + * @param $context + * The selection made using ctools_context_selector(). + * + * @return ctools_context|bool + * FALSE if the $required is not an object. A ctools_context object if one + * matched. + */ function _ctools_context_select($contexts, $required, $context) { if (!is_object($required)) { return FALSE; @@ -580,16 +948,14 @@ function _ctools_context_select($contexts, $required, $context) { /** * Create a new context object. * - * @param $type + * @param string $type * The type of context to create; this loads a plugin. - * @param $data + * @param mixed $data * The data to put into the context. - * @param $empty - * Whether or not this context is specifically empty. * @param $conf * A configuration structure if this context was created via UI. * - * @return + * @return ctools_context * A $context or NULL if one could not be created. */ function ctools_context_create($type, $data = NULL, $conf = FALSE) { @@ -612,7 +978,7 @@ function ctools_context_create($type, $data = NULL, $conf = FALSE) { * @param $type * The type of context to create; this loads a plugin. * - * @return + * @return ctools_context * A $context or NULL if one could not be created. */ function ctools_context_create_empty($type) { @@ -629,8 +995,21 @@ function ctools_context_create_empty($type) { /** * Perform keyword and context substitutions. + * + * @param string $string + * The string in which to replace keywords. + * @param array $keywords + * Array of keyword-replacement pairs. + * @param array $contexts + * + * @param array $converter_options + * Options to pass on to ctools_context_convert_context(), defaults to an + * empty array. + * + * @return string + * The returned string, with substitutions performed. */ -function ctools_context_keyword_substitute($string, $keywords, $contexts, $converter_options = array()) { +function ctools_context_keyword_substitute($string, $keywords, $contexts, array $converter_options = array()) { // Ensure a default keyword exists: $keywords['%%'] = '%'; @@ -653,7 +1032,7 @@ function ctools_context_keyword_substitute($string, $keywords, $contexts, $conve // If the keyword is already set by something passed in, don't try to // overwrite it. - if (!empty($keywords['%' . $keyword])) { + if (array_key_exists('%' . $keyword, $keywords)) { continue; } @@ -673,14 +1052,21 @@ function ctools_context_keyword_substitute($string, $keywords, $contexts, $conve } } - if (empty($context_keywords[$context]) || !empty($context_keywords[$context]->empty)) { - $keywords['%' . $keyword] = ''; - } - else if (!empty($converter)) { - $keywords['%' . $keyword] = ctools_context_convert_context($context_keywords[$context], $converter, $converter_options); + if (!isset($context_keywords[$context])) { + $keywords['%' . $keyword] = '%' . $keyword; } else { - $keywords['%' . $keyword] = $context_keywords[$keyword]->title; + if (empty($context_keywords[$context]) || !empty($context_keywords[$context]->empty)) { + $keywords['%' . $keyword] = ''; + } + else { + if (!empty($converter)) { + $keywords['%' . $keyword] = ctools_context_convert_context($context_keywords[$context], $converter, $converter_options); + } + else { + $keywords['%' . $keyword] = $context_keywords[$keyword]->title; + } + } } } } @@ -688,18 +1074,22 @@ function ctools_context_keyword_substitute($string, $keywords, $contexts, $conve } /** - * Determine a unique context ID for a context + * Determine a unique context ID for a context. * * Often contexts of many different types will be placed into a list. This * ensures that even though contexts of multiple types may share IDs, they * are unique in the final list. */ function ctools_context_id($context, $type = 'context') { - if (!$context['id']) { + // If not set, FALSE or empty. + if (!isset($context['id']) || !$context['id']) { $context['id'] = 1; } - return $type . '_' . $context['name'] . '_' . $context['id']; + // @todo is '' the appropriate default value? + $name = isset($context['name']) ? $context['name'] : ''; + + return $type . '_' . $name . '_' . $context['id']; } /** @@ -707,42 +1097,46 @@ function ctools_context_id($context, $type = 'context') { * * This finds the next id available for the named object. * - * @param $objects - * A list of context descriptor objects, i.e, arguments, relationships, contexts, etc. - * @param $name + * @param array $objects + * A list of context descriptor objects, i.e, arguments, relationships, + * contexts, etc. + * @param string $name * The name being used. + * + * @return int + * The next integer id available. */ function ctools_context_next_id($objects, $name) { $id = 0; - // Figure out which instance of this argument we're creating + // Figure out which instance of this argument we're creating. if (!$objects) { return $id + 1; } foreach ($objects as $object) { - if (isset($object['name']) && $object['name'] == $name) { - if ($object['id'] > $id) { + if (isset($object['name']) && $object['name'] === $name) { + if (isset($object['id']) && $object['id'] > $id) { $id = $object['id']; } + // @todo If obj has no 'id', should we increment local id? $id = $id + 1; } } return $id + 1; } - // --------------------------------------------------------------------------- // Functions related to contexts from arguments. - /** - * Fetch metadata on a specific argument plugin. + * Fetch metadata for a specific argument plugin. * * @param $argument * Name of an argument plugin. * - * @return + * @return array * An array with information about the requested argument plugin. */ + function ctools_get_argument($argument) { ctools_include('plugins'); return ctools_get_plugins('ctools', 'arguments', $argument); @@ -751,7 +1145,7 @@ function ctools_get_argument($argument) { /** * Fetch metadata for all argument plugins. * - * @return + * @return array * An array of arrays with information about all available argument plugins. */ function ctools_get_arguments() { @@ -771,22 +1165,23 @@ function ctools_get_arguments() { * - keyword: The keyword used for this argument for substitutions. * * @param $arg - * The actual argument received. This is expected to be a string from a URL but - * this does not have to be the only source of arguments. + * The actual argument received. This is expected to be a string from a URL + * but this does not have to be the only source of arguments. * @param $empty * If true, the $arg will not be used to load the context. Instead, an empty * placeholder context will be loaded. * - * @return + * @return ctools_context * A context object if one can be loaded. */ function ctools_context_get_context_from_argument($argument, $arg, $empty = FALSE) { ctools_include('plugins'); if (empty($argument['name'])) { - return; + return NULL; } - if ($function = ctools_plugin_load_function('ctools', 'arguments', $argument['name'], 'context')) { + $function = ctools_plugin_load_function('ctools', 'arguments', $argument['name'], 'context'); + if ($function) { // Backward compatibility: Merge old style settings into new style: if (!empty($argument['settings'])) { $argument += $argument['settings']; @@ -798,8 +1193,8 @@ function ctools_context_get_context_from_argument($argument, $arg, $empty = FALS if (is_object($context)) { $context->identifier = $argument['identifier']; $context->page_title = isset($argument['title']) ? $argument['title'] : ''; - $context->keyword = $argument['keyword']; - $context->id = ctools_context_id($argument, 'argument'); + $context->keyword = $argument['keyword']; + $context->id = ctools_context_id($argument, 'argument'); $context->original_argument = $arg; if (!empty($context->empty)) { @@ -815,6 +1210,12 @@ function ctools_context_get_context_from_argument($argument, $arg, $empty = FALS /** * Retrieve a list of empty contexts for all arguments. + * + * @param array $arguments + * + * @return array + * + * @see ctools_context_get_context_from_arguments() */ function ctools_context_get_placeholders_from_argument($arguments) { $contexts = array(); @@ -830,19 +1231,21 @@ function ctools_context_get_placeholders_from_argument($arguments) { /** * Load the contexts for a given list of arguments. * - * @param $arguments + * @param array $arguments * The array of argument definitions. - * @param &$contexts + * @param array &$contexts * The array of existing contexts. New contexts will be added to this array. - * @param $args + * @param array $args * The arguments to load. * - * @return - * FALSE if an argument wants to 404. + * @return bool + * TRUE if all is well, FALSE if an argument wants to 404. + * + * @see ctools_context_get_context_from_argument() */ function ctools_context_get_context_from_arguments($arguments, &$contexts, $args) { foreach ($arguments as $argument) { - // pull the argument off the list. + // Pull the argument off the list. $arg = array_shift($args); $id = ctools_context_id($argument, 'argument'); @@ -857,7 +1260,10 @@ function ctools_context_get_context_from_arguments($arguments, &$contexts, $args $context = $contexts[$id]; } - if ((empty($context) || empty($context->data)) && !empty($argument['default']) && $argument['default'] == '404') { + if ((empty($context) || empty($context->data)) + && !empty($argument['default']) + && $argument['default'] === '404' + ) { return FALSE; } } @@ -866,16 +1272,18 @@ function ctools_context_get_context_from_arguments($arguments, &$contexts, $args // --------------------------------------------------------------------------- // Functions related to contexts from relationships. - /** - * Fetch metadata on a specific relationship plugin. + * Fetch plugin metadata for a specific relationship plugin. * - * @param $content type + * @param $relationship * Name of a panel content type. * - * @return + * @return array * An array with information about the requested relationship. + * + * @see ctools_get_relationships() */ + function ctools_get_relationship($relationship) { ctools_include('plugins'); return ctools_get_plugins('ctools', 'relationships', $relationship); @@ -884,8 +1292,10 @@ function ctools_get_relationship($relationship) { /** * Fetch metadata for all relationship plugins. * - * @return + * @return array * An array of arrays with information about all available relationships. + * + * @see ctools_get_relationship() */ function ctools_get_relationships() { ctools_include('plugins'); @@ -893,8 +1303,9 @@ function ctools_get_relationships() { } /** + * Return a context from a relationship. * - * @param $relationship + * @param array $relationship * The configuration of a relationship. It must contain the following data: * - name: The name of the relationship plugin being used. * - relationship_settings: The configuration based upon the plugin forms. @@ -902,18 +1313,21 @@ function ctools_get_relationships() { * defined by the UI. * - keyword: The keyword used for this relationship for substitutions. * - * @param $source_context + * @param ctools_context $source_context * The context this relationship is based upon. - * - * @param $placeholders + * @param bool $placeholders * If TRUE, placeholders are acceptable. * - * @return - * A context object if one can be loaded. + * @return ctools_context|null + * A context object if one can be loaded, otherwise NULL. + * + * @see ctools_context_get_relevant_relationships() + * @see ctools_context_get_context_from_relationships() */ function ctools_context_get_context_from_relationship($relationship, $source_context, $placeholders = FALSE) { ctools_include('plugins'); - if ($function = ctools_plugin_load_function('ctools', 'relationships', $relationship['name'], 'context')) { + $function = ctools_plugin_load_function('ctools', 'relationships', $relationship['name'], 'context'); + if ($function) { // Backward compatibility: Merge old style settings into new style: if (!empty($relationship['relationship_settings'])) { $relationship += $relationship['relationship_settings']; @@ -924,7 +1338,7 @@ function ctools_context_get_context_from_relationship($relationship, $source_con if ($context) { $context->identifier = $relationship['identifier']; $context->page_title = isset($relationship['title']) ? $relationship['title'] : ''; - $context->keyword = $relationship['keyword']; + $context->keyword = $relationship['keyword']; if (!empty($context->empty)) { $context->placeholder = array( 'type' => 'relationship', @@ -934,6 +1348,7 @@ function ctools_context_get_context_from_relationship($relationship, $source_con return $context; } } + return NULL; } /** @@ -947,18 +1362,24 @@ function ctools_context_get_context_from_relationship($relationship, $source_con * @param $contexts * An array of contexts used to figure out which relationships are relevant. * - * @return + * @return array * An array of relationship keys that are relevant for the given set of * contexts. + * + * @see ctools_context_filter() + * @see ctools_context_get_context_from_relationship() + * @see ctools_context_get_context_from_relationships() */ function ctools_context_get_relevant_relationships($contexts) { $relevant = array(); $relationships = ctools_get_relationships(); - // Go through each relationship + // Go through each relationship. foreach ($relationships as $rid => $relationship) { // For each relationship, see if there is a context that satisfies it. - if (empty($relationship['no ui']) && ctools_context_filter($contexts, $relationship['required context'])) { + if (empty($relationship['no ui']) + && ctools_context_filter($contexts, $relationship['required context']) + ) { $relevant[$rid] = $relationship['title']; } } @@ -967,7 +1388,7 @@ function ctools_context_get_relevant_relationships($contexts) { } /** - * Fetch all active relationships + * Fetch all active relationships. * * @param $relationships * An keyed array of relationship data including: @@ -982,10 +1403,11 @@ function ctools_context_get_relevant_relationships($contexts) { * * @param $placeholders * If TRUE, placeholders are acceptable. + * + * @see ctools_context_get_context_from_relationship() + * @see ctools_context_get_relevant_relationships() */ function ctools_context_get_context_from_relationships($relationships, &$contexts, $placeholders = FALSE) { - $return = array(); - foreach ($relationships as $rdata) { if (!isset($rdata['context'])) { continue; @@ -1016,16 +1438,16 @@ function ctools_context_get_context_from_relationships($relationships, &$context // --------------------------------------------------------------------------- // Functions related to loading contexts from simple context definitions. - /** * Fetch metadata on a specific context plugin. * - * @param $context + * @param string $context * Name of a context. * - * @return + * @return array * An array with information about the requested panel context. */ + function ctools_get_context($context) { static $gate = array(); ctools_include('plugins'); @@ -1049,7 +1471,7 @@ function ctools_get_context($context) { /** * Fetch metadata for all context plugins. * - * @return + * @return array * An array of arrays with information about all available panel contexts. */ function ctools_get_contexts() { @@ -1058,27 +1480,38 @@ function ctools_get_contexts() { } /** + * Return a context object from a context definition array. * - * @param $context + * The input $context contains the information needed to identify and invoke + * the context plugin and create the plugin context from that. + * + * @param array $context * The configuration of a context. It must contain the following data: * - name: The name of the context plugin being used. * - context_settings: The configuration based upon the plugin forms. * - identifier: The human readable identifier for this context, usually * defined by the UI. * - keyword: The keyword used for this context for substitutions. - * @param $type + * @param string $type * This is either 'context' which indicates the context will be loaded - * from data in the settings, or 'required_context' which means the + * from data in the settings, or 'requiredcontext' which means the * context must be acquired from an external source. This is the method * used to pass pure contexts from one system to another. + * @param mixed $argument + * Optional information passed to the plugin context via the arg defined in + * the plugin's "placeholder name" field. * - * @return + * @return ctools_context|null * A context object if one can be loaded. + * + * @see ctools_get_context() + * @see ctools_plugin_get_function() */ function ctools_context_get_context_from_context($context, $type = 'context', $argument = NULL) { ctools_include('plugins'); $plugin = ctools_get_context($context['name']); - if ($function = ctools_plugin_get_function($plugin, 'context')) { + $function = ctools_plugin_get_function($plugin, 'context'); + if ($function) { // Backward compatibility: Merge old style settings into new style: if (!empty($context['context_settings'])) { $context += $context['context_settings']; @@ -1093,7 +1526,7 @@ function ctools_context_get_context_from_context($context, $type = 'context', $a if ($return) { $return->identifier = $context['identifier']; $return->page_title = isset($context['title']) ? $context['title'] : ''; - $return->keyword = $context['keyword']; + $return->keyword = $context['keyword']; if (!empty($context->empty)) { $context->placeholder = array( @@ -1105,6 +1538,8 @@ function ctools_context_get_context_from_context($context, $type = 'context', $a return $return; } } + + return NULL; } /** @@ -1118,7 +1553,10 @@ function ctools_context_get_context_from_context($context, $type = 'context', $a * Either 'context' or 'requiredcontext', which indicates whether the contexts * are loaded from internal data or copied from an external source. * @param $placeholders - * If true, placeholders are acceptable. + * If True, placeholders are acceptable. + * + * @return array + * Array of contexts, keyed by context ID. */ function ctools_context_get_context_from_contexts($contexts, $type = 'context', $placeholders = FALSE) { $return = array(); @@ -1137,17 +1575,20 @@ function ctools_context_get_context_from_contexts($contexts, $type = 'context', /** * Match up external contexts to our required contexts. * - * This function is used to create a list of contexts with proper - * IDs based upon a list of required contexts. + * This function is used to create a list of contexts with proper IDs based + * upon a list of required contexts. * - * These contexts passed in should match the numeric positions of the - * required contexts. The caller must ensure this has already happened - * correctly as this function will not detect errors here. + * These contexts passed in should match the numeric positions of the required + * contexts. The caller must ensure this has already happened correctly as this + * function will not detect errors here. * * @param $required * A list of required contexts as defined by the UI. * @param $contexts * A list of matching contexts as passed in from the calling system. + * + * @return array + * Array of contexts, keyed by context ID. */ function ctools_context_match_required_contexts($required, $contexts) { $return = array(); @@ -1156,10 +1597,10 @@ function ctools_context_match_required_contexts($required, $contexts) { } foreach ($required as $r) { - $context = clone(array_shift($contexts)); + $context = clone array_shift($contexts); $context->identifier = $r['identifier']; $context->page_title = isset($r['title']) ? $r['title'] : ''; - $context->keyword = $r['keyword']; + $context->keyword = $r['keyword']; $return[ctools_context_id($r, 'requiredcontext')] = $context; } @@ -1171,31 +1612,35 @@ function ctools_context_match_required_contexts($required, $contexts) { * * Not all of the types need to be supported by this object. * - * This function is not used to load contexts from external data, but may - * be used to load internal contexts and relationships. Otherwise it can also - * be used to generate a full set of placeholders for UI purposes. + * This function is not used to load contexts from external data, but may be + * used to load internal contexts and relationships. Otherwise it can also be + * used to generate a full set of placeholders for UI purposes. * - * @param $object + * @param object $object * An object that contains some or all of the following variables: * - * - requiredcontexts: A list of UI configured contexts that are required - * from an external source. Since these require external data, they will - * only be added if $placeholders is set to TRUE, and empty contexts will - * be created. - * - arguments: A list of UI configured arguments that will create contexts. - * Since these require external data, they will only be added if $placeholders - * is set to TRUE. - * - contexts: A list of UI configured contexts that have no external source, - * and are essentially hardcoded. For example, these might configure a - * particular node or a particular taxonomy term. - * - relationships: A list of UI configured contexts to be derived from other - * contexts that already exist from other sources. For example, these might - * be used to get a user object from a node via the node author relationship. - * @param $placeholders - * If TRUE, this will generate placeholder objects for types this function + * - requiredcontexts: A list of UI configured contexts that are required + * from an external source. Since these require external data, they will + * only be added if $placeholders is set to TRUE, and empty contexts will + * be created. + * - arguments: A list of UI configured arguments that will create contexts. + * As these require external data, they will only be added if $placeholders + * is set to TRUE. + * - contexts: A list of UI configured contexts that have no external source, + * and are essentially hardcoded. For example, these might configure a + * particular node or a particular taxonomy term. + * - relationships: A list of UI configured contexts to be derived from other + * contexts that already exist from other sources. For example, these might + * be used to get a user object from a node via the node author + * relationship. + * @param bool $placeholders + * If True, this will generate placeholder objects for any types this function * cannot load. - * @param $contexts + * @param array $contexts * An array of pre-existing contexts that will be part of the return value. + * + * @return array + * Merged output of all results of ctools_context_get_context_from_contexts(). */ function ctools_context_load_contexts($object, $placeholders = TRUE, $contexts = array()) { if (!empty($object->base_contexts)) { @@ -1219,7 +1664,7 @@ function ctools_context_load_contexts($object, $placeholders = TRUE, $contexts = $contexts += ctools_context_get_context_from_contexts($object->contexts, 'context', $placeholders); } - // add contexts from relationships + // Add contexts from relationships. if (!empty($object->relationships) && is_array($object->relationships)) { ctools_context_get_context_from_relationships($object->relationships, $contexts, $placeholders); } @@ -1238,7 +1683,7 @@ function ctools_context_load_contexts($object, $placeholders = TRUE, $contexts = function ctools_context_get_form($contexts) { if (!empty($contexts)) { foreach ($contexts as $id => $context) { - // if a form shows its id as being a 'required context' that means the + // If a form shows its id as being a 'required context' that means the // the context is external to this display and does not count. if (!empty($context->form_id) && substr($id, 0, 15) != 'requiredcontext') { return $context; @@ -1257,7 +1702,7 @@ function ctools_context_get_form($contexts) { * The arguments. These will be acquired from $form_state['values'] and the * keys must match the context IDs. * - * @return + * @return array * A new $contexts array containing the replaced contexts. Not all contexts * may be replaced if, for example, an argument was unable to be converted * into a context. @@ -1276,12 +1721,14 @@ function ctools_context_replace_placeholders($contexts, $arguments) { $new_context = ctools_context_get_context_from_relationship($relationship, $contexts[$relationship['context']]); } break; + case 'argument': if (isset($arguments[$cid]) && $arguments[$cid] !== '') { $argument = $context->placeholder['conf']; $new_context = ctools_context_get_context_from_argument($argument, $arguments[$cid]); } break; + case 'context': if (!empty($arguments[$cid])) { $context_info = $context->placeholder['conf']; @@ -1330,32 +1777,37 @@ function ctools_context_replace_form(&$form, $contexts) { if (is_array($plugin['placeholder form'])) { $form[$cid] = $plugin['placeholder form']; } - else if (function_exists($plugin['placeholder form'])) { - $widget = $plugin['placeholder form']($info); - if ($widget) { - $form[$cid] = $widget; + else { + if (function_exists($plugin['placeholder form'])) { + $widget = $plugin['placeholder form']($info); + if ($widget) { + $form[$cid] = $widget; + } } } if (!empty($form[$cid])) { - $form[$cid]['#title'] = t('@identifier (@keyword)', array('@keyword' => '%' . $context->keyword, '@identifier' => $context->identifier)); + $form[$cid]['#title'] = t('@identifier (@keyword)', array( + '@keyword' => '%' . $context->keyword, + '@identifier' => $context->identifier, + )); } } } } // --------------------------------------------------------------------------- -// Functions related to loading access control plugins - +// Functions related to loading access control plugins. /** * Fetch metadata on a specific access control plugin. * * @param $name * Name of a plugin. * - * @return + * @return array * An array with information about the requested access control plugin. */ + function ctools_get_access_plugin($name) { ctools_include('plugins'); return ctools_get_plugins('ctools', 'access', $name); @@ -1364,7 +1816,7 @@ function ctools_get_access_plugin($name) { /** * Fetch metadata for all access control plugins. * - * @return + * @return array * An array of arrays with information about all available access control plugins. */ function ctools_get_access_plugins() { @@ -1376,8 +1828,14 @@ function ctools_get_access_plugins() { * Fetch a list of access plugins that are available for a given list of * contexts. * - * if 'logged-in-user' is not in the list of contexts, it will be added as + * If 'logged-in-user' is not in the list of contexts, it will be added as * this is required. + * + * @param array $contexts + * Array of ctools_context objects with which to select access plugins. + * + * @return array + * Array of applicable access plugins. Can be empty. */ function ctools_get_relevant_access_plugins($contexts) { if (!isset($contexts['logged-in-user'])) { @@ -1387,7 +1845,9 @@ function ctools_get_relevant_access_plugins($contexts) { $all_plugins = ctools_get_access_plugins(); $plugins = array(); foreach ($all_plugins as $id => $plugin) { - if (!empty($plugin['required context']) && !ctools_context_match_requirements($contexts, $plugin['required context'])) { + if (!empty($plugin['required context']) + && !ctools_context_match_requirements($contexts, $plugin['required context']) + ) { continue; } $plugins[$id] = $plugin; @@ -1400,17 +1860,19 @@ function ctools_get_relevant_access_plugins($contexts) { * Create a context for the logged in user. */ function ctools_access_get_loggedin_context() { - global $user; - $context = ctools_context_create('entity:user', $user); + $context = ctools_context_create('entity:user', array('type' => 'current'), TRUE); $context->identifier = t('Logged in user'); - $context->keyword = 'viewer'; - $context->id = 0; + $context->keyword = 'viewer'; + $context->id = 0; return $context; } /** * Get a summary of an access plugin's settings. + * + * @return string + * The summary text. */ function ctools_access_summary($plugin, $contexts, $test) { if (!isset($contexts['logged-in-user'])) { @@ -1420,8 +1882,9 @@ function ctools_access_summary($plugin, $contexts, $test) { $description = ''; if ($function = ctools_plugin_get_function($plugin, 'summary')) { $required_context = isset($plugin['required context']) ? $plugin['required context'] : array(); - $context = isset($test['context']) ? $test['context'] : array(); - $description = $function($test['settings'], ctools_context_select($contexts, $required_context, $context), $plugin); + $context = isset($test['context']) ? $test['context'] : array(); + $selected_context = ctools_context_select($contexts, $required_context, $context); + $description = $function($test['settings'], $selected_context, $plugin); } if (!empty($test['not'])) { @@ -1433,10 +1896,23 @@ function ctools_access_summary($plugin, $contexts, $test) { /** * Get a summary of a group of access plugin's settings. + * + * @param $access + * An array of settings theoretically set by the user, including the array + * of plugins to check: + * - 'plugins': the array of plugin metadata info to check + * - 'logic': (optional) either 'and' or 'or', indicating how to combine + * restrictions. Defaults to 'or'. + * @param array $contexts + * An array of zero or more contexts that may be used to determine if + * the user has access. + * + * @return string + * The summary text. Can be NULL if there are no plugins defined. */ function ctools_access_group_summary($access, $contexts) { - if (empty($access['plugins'])) { - return; + if (empty($access['plugins']) || !is_array($access['plugins'])) { + return NULL; } $descriptions = array(); @@ -1445,21 +1921,29 @@ function ctools_access_group_summary($access, $contexts) { $descriptions[] = ctools_access_summary($plugin, $contexts, $test); } - $separator = (isset($access['logic']) && $access['logic'] == 'and') ? t(', and ') : t(', or '); + $separator = + (isset($access['logic']) && $access['logic'] === 'and') + ? t(', and ') : t(', or '); return implode($separator, $descriptions); } /** - * Determine if the current user has access via plugin. + * Determine if the current user has access via a plugin. * - * @param $settings - * An array of settings theoretically set by the user. - * @param $contexts + * @param array $settings + * An array of settings theoretically set by the user, including the array + * of plugins to check: + * - 'plugins': the array of plugin metadata info to check + * - 'logic': (optional) either 'and' or 'or', indicating how to combine + * restrictions. The 'or' case is not fully implemented and returns the + * input contexts unchanged. + * + * @param array $contexts * An array of zero or more contexts that may be used to determine if * the user has access. * - * @return - * TRUE if access is granted, false if otherwise. + * @return bool + * TRUE if access is granted, FALSE if otherwise. */ function ctools_access($settings, $contexts = array()) { if (empty($settings['plugins'])) { @@ -1498,15 +1982,15 @@ function ctools_access($settings, $contexts = array()) { // Pass if 'or' and this rule passed. return TRUE; } - else if (!$pass && $settings['logic'] == 'and') { - // Fail if 'and' and htis rule failed. + elseif (!$pass && $settings['logic'] == 'and') { + // Fail if 'and' and this rule failed. return FALSE; } } // Return TRUE if logic was and, meaning all rules passed. // Return FALSE if logic was or, meaning no rule passed. - return $settings['logic'] == 'and'; + return ($settings['logic'] === 'and'); } /** @@ -1515,7 +1999,7 @@ function ctools_access($settings, $contexts = array()) { * @param $plugin * The access plugin being used. * - * @return + * @return array * A default configured test that should be placed in $access['plugins']; */ function ctools_access_new_test($plugin) { @@ -1537,7 +2021,6 @@ function ctools_access_new_test($plugin) { } } - $default = NULL; if (isset($plugin['default'])) { $default = $plugin['default']; @@ -1551,7 +2034,7 @@ function ctools_access_new_test($plugin) { if (is_array($default)) { $test['settings'] = $default; } - else if (function_exists($default)) { + elseif (function_exists($default)) { $test['settings'] = $default(); } else { @@ -1565,11 +2048,23 @@ function ctools_access_new_test($plugin) { /** * Apply restrictions to contexts based upon the access control configured. * - * These restrictions allow the UI to not show content that may not - * be relevant to all types of a particular context. + * These restrictions allow the UI to not show content that may not be relevant + * to all types of a particular context. + * + * @param array $settings + * Array of keys specifying the settings: + * - 'plugins': the array of plugin metadata info to check. If not set, or + * not an array, the function returns with no action. + * - 'logic': (optional) either 'and' or 'or', indicating how to combine + * restrictions. Defaults to 'and'. + * The 'or' case is not fully implemented and returns with no action if + * there is more than one plugin. + * + * @param array $contexts + * Array of available contexts. */ function ctools_access_add_restrictions($settings, $contexts) { - if (empty($settings['plugins'])) { + if (empty($settings['plugins']) || !is_array($settings['plugins'])) { return; } @@ -1578,17 +2073,23 @@ function ctools_access_add_restrictions($settings, $contexts) { } // We're not going to try to figure out restrictions on the or. - if ($settings['logic'] == 'or' && count($settings['plugins']) > 1) { + if ($settings['logic'] === 'or' && count($settings['plugins']) > 1) { return; } foreach ($settings['plugins'] as $test) { $plugin = ctools_get_access_plugin($test['name']); - if ($plugin && $function = ctools_plugin_get_function($plugin, 'restrictions')) { + // $plugin is 'array()' on error. + if ($plugin + && $function = ctools_plugin_get_function($plugin, 'restrictions') + ) { $required_context = isset($plugin['required context']) ? $plugin['required context'] : array(); $context = isset($test['context']) ? $test['context'] : array(); $contexts = ctools_context_select($contexts, $required_context, $context); - $function($test['settings'], $contexts); + + if ($contexts !== FALSE) { + $function($test['settings'], $contexts); + } } } } diff --git a/sites/all/modules/contrib/dev/ctools/includes/context.menu.inc b/sites/all/modules/contrib/dev/ctools/includes/context.menu.inc index ee227cb..798d167 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/context.menu.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/context.menu.inc @@ -25,7 +25,7 @@ function ctools_context_menu(&$items) { 'page callback' => 'ctools_context_ajax_item_delete', ) + $base; - // For the access system + // For the access system. $base['file'] = 'includes/context-access-admin.inc'; $items['ctools/context/ajax/access/add'] = array( 'page callback' => 'ctools_access_ajax_add', diff --git a/sites/all/modules/contrib/dev/ctools/includes/context.theme.inc b/sites/all/modules/contrib/dev/ctools/includes/context.theme.inc index 8f660b8..d0d866f 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/context.theme.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/context.theme.inc @@ -19,7 +19,6 @@ function ctools_context_theme(&$theme) { ); $theme['ctools_context_item_form'] = array( 'render element' => 'form', -// 'variables' => array('form' => NULL), 'file' => 'includes/context.theme.inc', ); $theme['ctools_context_item_row'] = array( @@ -27,7 +26,7 @@ function ctools_context_theme(&$theme) { 'file' => 'includes/context.theme.inc', ); - // For the access plugin + // For the access plugin. $theme['ctools_access_admin_add'] = array( 'render element' => 'form', 'file' => 'includes/context-access-admin.inc', @@ -62,10 +61,10 @@ function theme_ctools_context_item_row($vars) { function theme_ctools_context_item_form($vars) { $form = $vars['form']; - $output = ''; - $type = $form['#ctools_context_type']; - $module = $form['#ctools_context_module']; - $cache_key = $form['#cache_key']; + $output = ''; + $type = $form['#ctools_context_type']; + $module = $form['#ctools_context_module']; + $cache_key = $form['#cache_key']; $type_info = ctools_context_info($type); @@ -104,17 +103,17 @@ function theme_ctools_context_item_form($vars) { if (!empty($form['buttons'])) { // Display the add context item. - $row = array(); - $row[] = array('data' => render($form['buttons'][$type]['item']), 'class' => array('title')); - $row[] = array('data' => render($form['buttons'][$type]['add']), 'class' => array('add'), 'width' => "60%"); - $output .= '
    '; - $output .= render($form['buttons'][$type]); - $theme_vars = array(); - $theme_vars['header'] = array(); - $theme_vars['rows'] = array($row); + $row = array(); + $row[] = array('data' => render($form['buttons'][$type]['item']), 'class' => array('title')); + $row[] = array('data' => render($form['buttons'][$type]['add']), 'class' => array('add'), 'width' => "60%"); + $output .= '
    '; + $output .= render($form['buttons'][$type]); + $theme_vars = array(); + $theme_vars['header'] = array(); + $theme_vars['rows'] = array($row); $theme_vars['attributes'] = array('id' => $type . '-add-table'); - $output .= theme('table', $theme_vars); - $output .= '
    '; + $output .= theme('table', $theme_vars); + $output .= '
    '; } if (!empty($form['description'])) { $output .= render($form['description']); @@ -139,7 +138,7 @@ function theme_ctools_context_list($vars) { $description = (!empty($vars['description'])) ? $vars['description'] : NULL; $titles = array(); $output = ''; - $count = 1; + $count = 1; $contexts = ctools_context_load_contexts($object); @@ -209,7 +208,7 @@ function theme_ctools_context_list($vars) { } } - // And relationships + // And relationships. if (!empty($object->relationships)) { foreach ($object->relationships as $relationship) { $output .= ''; @@ -253,15 +252,15 @@ function theme_ctools_context_list($vars) { } /** - * ctools_context_list() but not in a table format because tabledrag - * won't let us have tables within tables and still drag. + * The ctools_context_list() function but not in a table format because + * tabledrag won't let us have tables within tables and still drag. */ function theme_ctools_context_list_no_table($vars) { $object = $vars['object']; ctools_add_css('context'); $titles = array(); $output = ''; - $count = 1; + $count = 1; // Describe 'built in' contexts. if (!empty($object->base_contexts)) { foreach ($object->base_contexts as $id => $context) { @@ -312,7 +311,7 @@ function theme_ctools_context_list_no_table($vars) { $count++; } } - // And relationships + // And relationships. if (!empty($object->relationships)) { foreach ($object->relationships as $relationship) { $output .= '
    '; @@ -341,4 +340,3 @@ function theme_ctools_context_list_no_table($vars) { return $output; } - diff --git a/sites/all/modules/contrib/dev/ctools/includes/css-cache.inc b/sites/all/modules/contrib/dev/ctools/includes/css-cache.inc new file mode 100644 index 0000000..d88160b --- /dev/null +++ b/sites/all/modules/contrib/dev/ctools/includes/css-cache.inc @@ -0,0 +1,52 @@ +key(array('cid' => $id)) ->fields(array( - 'cid' => $id, 'filename' => $filename, 'css' => $css, 'filter' => intval($filter), @@ -149,11 +149,11 @@ function ctools_css_clear($id) { * * @param $css * A chunk of well-formed CSS text to cache. - * @param $filter + * @param bool $filter * If TRUE the css will be filtered. If FALSE the text will be cached * as-is. * - * @return $filename + * @return string * The filename the CSS will be cached in. */ function ctools_css_cache($css, $filter = TRUE) { @@ -164,7 +164,6 @@ function ctools_css_cache($css, $filter = TRUE) { // Create the css/ within the files folder. $path = 'public://ctools/css'; if (!file_prepare_directory($path, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) { -// if (!file_prepare_directory($path, FILE_CREATE_DIRECTORY)) { drupal_set_message(t('Unable to create CTools CSS cache directory. Check the permissions on your files directory.'), 'error'); return; } @@ -172,10 +171,12 @@ function ctools_css_cache($css, $filter = TRUE) { // @todo Is this slow? Does it matter if it is? $filename = $path . '/' . md5($css) . '.css'; - // This will do renames if the file already exists, ensuring we don't - // accidentally overwrite other files who share the same md5. Yes this - // is a very miniscule chance but it's safe. - $filename = file_unmanaged_save_data($css, $filename); + // Generally md5 is considered unique enough to sign file downloads. + // So this replaces already existing files based on the assumption that two + // files with the same hash are identical content wise. + // If we rename, the cache folder can potentially fill up with thousands of + // files with the same content. + $filename = file_unmanaged_save_data($css, $filename, FILE_EXISTS_REPLACE); return $filename; } @@ -211,7 +212,7 @@ function ctools_css_filter($css, $compressed = TRUE) { * An array of css data, as produced by @see ctools_css_disassemble() * disassembler and the @see ctools_css_filter_css_data() filter. * - * @return string $css + * @return string * css optimized for human viewing. */ function ctools_css_assemble($css_data) { @@ -241,7 +242,7 @@ function ctools_css_assemble($css_data) { * An array of css data, as produced by @see ctools_css_disassemble() * disassembler and the @see ctools_css_filter_css_data() filter. * - * @return string $css + * @return string * css optimized for use. */ function ctools_css_compress($css_data) { @@ -277,7 +278,7 @@ function ctools_css_compress($css_data) { * @param string $css * A string containing the css to be disassembled. * - * @return array $disassembled_css + * @return array * An array of disassembled, slightly cleaned-up/formatted css statements. */ function ctools_css_disassemble($css) { @@ -383,7 +384,6 @@ function _ctools_css_disassemble_declaration($declaration) { * An array of disassembled, filtered CSS. */ function ctools_css_filter_css_data($css, $allowed_properties = array(), $allowed_values = array(), $allowed_values_regex = '', $disallowed_values_regex = '') { -//function ctools_css_filter_css_data($css, &$filtered = NULL, $allowed_properties = array(), $allowed_values = array(), $allowed_values_regex = '', $disallowed_values_regex = '') { // Retrieve the default list of allowed properties if none is provided. $allowed_properties = !empty($allowed_properties) ? $allowed_properties : ctools_css_filter_default_allowed_properties(); // Retrieve the default list of allowed values if none is provided. @@ -391,19 +391,18 @@ function ctools_css_filter_css_data($css, $allowed_properties = array(), $allowe // Define allowed values regex if none is provided. $allowed_values_regex = !empty($allowed_values_regex) ? $allowed_values_regex : '/(#[0-9a-f]+|rgb\(\d+%?,\d*%?,?\d*%?\)?|\d{0,2}\.?\d{0,2}(cm|em|ex|in|mm|pc|pt|px|%|,|\))?)/'; // Define disallowed url() value contents, if none is provided. - // $disallowed_values_regex = !empty($disallowed_values_regex) ? $disallowed_values_regex : '/[url|expression]\s*\(\s*[^\s)]+?\s*\)\s*/'; $disallowed_values_regex = !empty($disallowed_values_regex) ? $disallowed_values_regex : '/(url|expression)/'; foreach ($css as $selector_str => $declaration) { foreach ($declaration as $property => $value) { if (!in_array($property, $allowed_properties)) { - // $filtered['properties'][$selector_str][$property] = $value; + // $filtered['properties'][$selector_str][$property] = $value;. unset($css[$selector_str][$property]); continue; } $value = str_replace('!important', '', $value); if (preg_match($disallowed_values_regex, $value) || !(in_array($value, $allowed_values) || preg_match($allowed_values_regex, $value))) { - // $filtered['values'][$selector_str][$property] = $value; + // $filtered['values'][$selector_str][$property] = $value;. unset($css[$selector_str][$property]); continue; } diff --git a/sites/all/modules/contrib/dev/ctools/includes/dependent.inc b/sites/all/modules/contrib/dev/ctools/includes/dependent.inc index 74de919..20c25de 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/dependent.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/dependent.inc @@ -44,7 +44,7 @@ * * A fuller example, that hides the menu title when no menu is selected: * @code - *function ctools_dependent_example() { + * function ctools_dependent_example() { * $form = array(); * $form['menu'] = array( * '#type' => 'fieldset', @@ -72,12 +72,12 @@ * ); * * return system_settings_form($form); - *} + * } * @endcode * * An example for hiding checkboxes using #prefix and #suffix: * @code - *function ctools_dependent_example_checkbox() { + * function ctools_dependent_example_checkbox() { * $form = array(); * $form['object'] = array( * '#type' => 'fieldset', @@ -111,7 +111,7 @@ * ); * * return system_settings_form($form); - *} + * } * @endcode * * Deprecated: @@ -125,7 +125,6 @@ /** * Process callback to add dependency to form items. - * */ function ctools_dependent_process($element, &$form_state, &$form) { return $element; diff --git a/sites/all/modules/contrib/dev/ctools/includes/dropbutton.theme.inc b/sites/all/modules/contrib/dev/ctools/includes/dropbutton.theme.inc index fcdd5a3..4b95ef0 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/dropbutton.theme.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/dropbutton.theme.inc @@ -75,7 +75,7 @@ function theme_links__ctools_dropbutton($vars) { if (!empty($vars['links'])) { $is_drop_button = (count($vars['links']) > 1); - // Add needed files + // Add needed files. if ($is_drop_button) { ctools_add_js('dropbutton'); ctools_add_css('dropbutton'); @@ -86,7 +86,7 @@ function theme_links__ctools_dropbutton($vars) { static $id = 0; $id++; - // Wrapping div + // Wrapping div. $class = 'ctools-no-js'; $class .= ($is_drop_button) ? ' ctools-dropbutton' : ''; $class .= ' ctools-button'; @@ -98,21 +98,22 @@ function theme_links__ctools_dropbutton($vars) { $output .= '
    '; - // Add a twisty if this is a dropbutton + // Add a twisty if this is a dropbutton. if ($is_drop_button) { $vars['title'] = ($vars['title'] ? check_plain($vars['title']) : t('open')); $output .= ''; // ctools-link + // ctools-link. + $output .= '
    '; } - // The button content + // The button content. $output .= '
    '; // Check for attributes. theme_links expects an array(). @@ -132,12 +133,11 @@ function theme_links__ctools_dropbutton($vars) { // Call theme_links to render the list of links. $output .= theme_links(array('links' => $vars['links'], 'attributes' => $vars['attributes'], 'heading' => '')); - $output .= '
    '; // ctools-content - $output .= '
    '; // ctools-dropbutton + $output .= '
    '; // ctools-content. + $output .= ''; // ctools-dropbutton. return $output; } else { return ''; } } - diff --git a/sites/all/modules/contrib/dev/ctools/includes/dropdown.theme.inc b/sites/all/modules/contrib/dev/ctools/includes/dropdown.theme.inc index 7e748f5..b763687 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/dropdown.theme.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/dropdown.theme.inc @@ -44,20 +44,21 @@ function ctools_dropdown_theme(&$items) { /** * Create a dropdown menu. * - * @param $title - * The text to place in the clickable area to activate the dropdown. - * @param $links - * A list of links to provide within the dropdown, suitable for use - * in via Drupal's theme('links'). - * @param $image - * If true, the dropdown link is an image and will not get extra decorations - * that a text dropdown link will. - * @param $class - * An optional class to add to the dropdown's container div to allow you - * to style a single dropdown however you like without interfering with - * other dropdowns. + * @param array $variables + * An associative array containing: + * - title: The text to place in the clickable area to activate the dropdown. + * - links: A list of links to provide within the dropdown, suitable for use + * in via Drupal's theme('links'). + * - image: If true, the dropdown link is an image and will not get extra + * decorations that a text dropdown link will. + * - class: An optional class to add to the dropdown's container div to allow + * you to style a single dropdown however you like without interfering with + * other dropdowns. + * + * @return string + * Returns HTML for a language configuration form. */ -function theme_ctools_dropdown($vars) { +function theme_ctools_dropdown($vars) { // Provide a unique identifier for every dropdown on the page. static $id = 0; $id++; @@ -67,9 +68,7 @@ function theme_ctools_dropdown($vars) { ctools_add_js('dropdown'); ctools_add_css('dropdown'); - $output = ''; - - $output .= '
    '; + $output = '
    '; $output .= ''; // wrapper + $output .= '
    '; $output .= '
    '; $output .= '
    '; $output .= theme_links(array('links' => $vars['links'], 'attributes' => array(), 'heading' => '')); - $output .= '
    '; // container - $output .= '
    '; // container wrapper - $output .= '
    '; // dropdown + $output .= ''; + $output .= ''; + $output .= ''; + return $output; } - diff --git a/sites/all/modules/contrib/dev/ctools/includes/entity-access.inc b/sites/all/modules/contrib/dev/ctools/includes/entity-access.inc new file mode 100644 index 0000000..69f2c33 --- /dev/null +++ b/sites/all/modules/contrib/dev/ctools/includes/entity-access.inc @@ -0,0 +1,154 @@ +nid); + if ($node->vid !== $default_revision->vid) { + return _node_revision_access($node, $op, $account); + } + else { + return node_access($op, $node, $account); + } + } + // No node is provided. Check for access to all nodes. + if (user_access('bypass node access', $account)) { + return TRUE; + } + if (!user_access('access content', $account)) { + return FALSE; + } + if ($op == 'view' && node_access_view_all_nodes($account)) { + return TRUE; + } + return FALSE; +} + +/** + * Access callback for the user entity. + */ +function ctools_metadata_user_access($op, $entity = NULL, $account = NULL, $entity_type) { + $account = isset($account) ? $account : $GLOBALS['user']; + // Grant access to the users own user account and to the anonymous one. + if (isset($entity) && $op != 'delete' && (($entity->uid == $account->uid && $entity->uid) || (!$entity->uid && $op == 'view'))) { + return TRUE; + } + if (user_access('administer users', $account) || user_access('access user profiles', $account) && $op == 'view' && $entity->status) { + return TRUE; + } + return FALSE; +} + +/** + * Access callback for the comment entity. + */ +function ctools_metadata_comment_access($op, $entity = NULL, $account = NULL) { + // When determining access to a comment, if comment has an associated node, + // the user must be able to view the node in order to access the comment. + if (isset($entity->nid)) { + if (!node_access('view', node_load($entity->nid), $account)) { + return FALSE; + } + } + + // Comment administrators are allowed to perform all operations on all + // comments. + if (user_access('administer comments', $account)) { + return TRUE; + } + + // Unpublished comments can never be accessed by non-admins. + if (isset($entity->status) && $entity->status == COMMENT_NOT_PUBLISHED) { + return FALSE; + } + + if (user_access('access comments', $account) && $op == 'view') { + return TRUE; + } + return FALSE; +} + +/** + * Access callback for the taxonomy entities. + */ +function ctools_metadata_taxonomy_access($op, $entity = NULL, $account = NULL, $entity_type) { + if ($entity_type == 'taxonomy_vocabulary') { + return user_access('administer taxonomy', $account); + } + if (user_access('administer taxonomy', $account) || user_access('access content', $account) && $op == 'view') { + return TRUE; + } + return FALSE; +} diff --git a/sites/all/modules/contrib/dev/ctools/includes/export-ui.inc b/sites/all/modules/contrib/dev/ctools/includes/export-ui.inc index 16e57d6..72e5a2f 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/export-ui.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/export-ui.inc @@ -81,7 +81,6 @@ function ctools_export_ui_process(&$plugin, $info) { // Add some default fields that appear often in exports // If these use different keys they can easily be specified in the // $plugin. - if (empty($plugin['export']['admin_title']) && !empty($schema['fields']['admin_title'])) { $plugin['export']['admin_title'] = 'admin_title'; } @@ -297,7 +296,6 @@ function ctools_export_ui_process(&$plugin, $info) { ); // Define strings. - // For all strings, %title may be filled in at a later time via str_replace // since we do not know the title now. $plugin['strings'] += array( @@ -335,7 +333,7 @@ function ctools_export_ui_process(&$plugin, $info) { $plugin['strings']['confirmation']['delete'] += array( 'question' => t('Are you sure you want to delete %title?'), - 'information' => t('This action will permanently remove this item from your database..'), + 'information' => t('This action will permanently remove this item from your database.'), 'success' => t('The item has been deleted.'), ); @@ -414,6 +412,7 @@ function ctools_export_ui_plugin_base_path($plugin) { * The id in the menu items from the plugin. * @param $export_key * The export key of the item being edited, if it exists. + * * @return * The menu path to the plugin's list. */ @@ -429,12 +428,11 @@ function ctools_export_ui_plugin_menu_path($plugin, $item_id, $export_key = NULL * Helper function to include CTools plugins and get an export-ui exportable. * * @param $plugin_name - * The plugin that should be laoded. + * The plugin that should be loaded. */ function ctools_get_export_ui($plugin_name) { ctools_include('plugins'); return ctools_get_plugins('ctools', 'export_ui', $plugin_name); - } /** @@ -456,14 +454,14 @@ function ctools_export_ui_switcher_page($plugin_name, $op) { $args = func_get_args(); $js = !empty($_REQUEST['js']); - // Load the $plugin information + // Load the $plugin information. $plugin = ctools_get_export_ui($plugin_name); $handler = ctools_export_ui_get_handler($plugin); if ($handler) { $method = $op . '_page'; if (method_exists($handler, $method)) { - // replace the first two arguments: + // Replace the first two arguments: $args[0] = $js; $args[1] = $_POST; return call_user_func_array(array($handler, $method), $args); diff --git a/sites/all/modules/contrib/dev/ctools/includes/export-ui.menu.inc b/sites/all/modules/contrib/dev/ctools/includes/export-ui.menu.inc index d27bf15..0fda8c2 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/export-ui.menu.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/export-ui.menu.inc @@ -1,5 +1,9 @@ array('handler'), ); -} \ No newline at end of file +} diff --git a/sites/all/modules/contrib/dev/ctools/includes/export.inc b/sites/all/modules/contrib/dev/ctools/includes/export.inc index 0b2ef46..d2fff2f 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/export.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/export.inc @@ -4,7 +4,7 @@ * @file * Contains code to make it easier to have exportable objects. * - * Documentation for exportable objects is contained in help/export.html + * Documentation for exportable objects is contained in help/export.html. */ /** @@ -126,6 +126,7 @@ function ctools_export_crud_load_multiple($table, array $names) { * If TRUE, the static cache of all objects will be flushed prior to * loading all. This can be important on listing pages where items * might have changed on the page load. + * * @return * An array of all loaded objects, keyed by the unique IDs of the export key. */ @@ -328,7 +329,6 @@ function ctools_export_crud_set_status($table, $object, $status) { } - /** * Enable a certain object. * @@ -426,7 +426,7 @@ function ctools_export_load_object($table, $type = 'all', $args = array()) { } } - // Build the query + // Build the query. $query = db_select($table, 't__0')->fields('t__0'); $alias_count = 1; if (!empty($schema['join'])) { @@ -451,7 +451,7 @@ function ctools_export_load_object($table, $type = 'all', $args = array()) { if ($type == 'names') { $query->condition($export['key'], $args, 'IN'); } - else if ($type == 'conditions') { + elseif ($type == 'conditions') { foreach ($args as $key => $value) { if (isset($schema['fields'][$key])) { $query->condition($key, $value); @@ -499,7 +499,7 @@ function ctools_export_load_object($table, $type = 'all', $args = array()) { if ($defaults) { foreach ($defaults as $object) { if ($type == 'conditions') { - // if this does not match all of our conditions, skip it. + // If this does not match all of our conditions, skip it. foreach ($args as $key => $value) { if (!isset($object->$key)) { continue 2; @@ -509,12 +509,12 @@ function ctools_export_load_object($table, $type = 'all', $args = array()) { continue 2; } } - else if ($object->$key != $value) { + elseif ($object->$key != $value) { continue 2; } } } - else if ($type == 'names') { + elseif ($type == 'names') { if (!in_array($object->{$export['key']}, $args)) { continue; } @@ -561,7 +561,6 @@ function ctools_export_load_object($table, $type = 'all', $args = array()) { } } - // For conditions, return $return; } @@ -807,10 +806,10 @@ function _ctools_export_get_some_defaults($table, $export, $names) { function _ctools_export_unpack_object($schema, $data, $object = 'stdClass') { if (is_string($object)) { if (class_exists($object)) { - $object = new $object; + $object = new $object(); } else { - $object = new stdClass; + $object = new stdClass(); } } @@ -873,14 +872,14 @@ function ctools_var_export($var, $prefix = '') { $output .= $prefix . ')'; } } - else if (is_object($var) && get_class($var) === 'stdClass') { + elseif (is_object($var) && get_class($var) === 'stdClass') { // var_export() will export stdClass objects using an undefined // magic method __set_state() leaving the export broken. This // workaround avoids this by casting the object as an array for // export and casting it back to an object when evaluated. $output = '(object) ' . ctools_var_export((array) $var, $prefix); } - else if (is_bool($var)) { + elseif (is_bool($var)) { $output = $var ? 'TRUE' : 'FALSE'; } else { @@ -902,7 +901,8 @@ function ctools_export_object($table, $object, $indent = '', $identifier = NULL, $output = $indent . '$' . $identifier . ' = new ' . get_class($object) . "();\n"; if ($schema['export']['can disable']) { - $output .= $indent . '$' . $identifier . '->disabled = FALSE; /* Edit this to true to make a default ' . $identifier . ' disabled initially */' . "\n"; + $disabled = !isset($object->disabled) || $object->disabled != TRUE ? 'FALSE' : 'TRUE'; + $output .= $indent . '$' . $identifier . '->disabled = ' . $disabled . '; /* Edit this to true to make a default ' . $identifier . ' disabled initially */' . "\n"; } if (!empty($schema['export']['api']['current_version'])) { $output .= $indent . '$' . $identifier . '->api_version = ' . $schema['export']['api']['current_version'] . ";\n"; @@ -945,14 +945,20 @@ function ctools_export_object($table, $object, $indent = '', $identifier = NULL, else { $value = $object->$field; if ($info['type'] == 'int') { - $value = (isset($info['size']) && $info['size'] == 'tiny') ? (bool) $value : (int) $value; + if (isset($info['size']) && $info['size'] == 'tiny') { + $info['boolean'] = (!isset($info['boolean'])) ? $schema['export']['boolean'] : $info['boolean']; + $value = ($info['boolean']) ? (bool) $value : (int) $value; + } + else { + $value = (int) $value; + } } $output .= $indent . '$' . $identifier . '->' . $field . ' = ' . ctools_var_export($value, $indent) . ";\n"; } } - // And bottom additions here + // And bottom additions here. foreach ($additions2 as $field => $value) { $output .= $indent . '$' . $identifier . '->' . $field . ' = ' . ctools_var_export($value, $indent) . ";\n"; } @@ -967,7 +973,12 @@ function ctools_export_object($table, $object, $indent = '', $identifier = NULL, * that it's easily available. */ function ctools_export_get_schema($table) { - $cache = &drupal_static(__FUNCTION__); + static $drupal_static_fast; + if (!isset($drupal_static_fast)) { + $drupal_static_fast['cache'] = &drupal_static(__FUNCTION__); + } + $cache = &$drupal_static_fast['cache']; + if (empty($cache[$table])) { $schema = drupal_get_schema($table); @@ -975,7 +986,7 @@ function ctools_export_get_schema($table) { // simply hasn't been cached. If we've been asked, let's force the // issue. if (!$schema || empty($schema['export'])) { - // force a schema reset: + // Force a schema reset: $schema = drupal_get_schema($table, TRUE); } @@ -987,7 +998,7 @@ function ctools_export_get_schema($table) { return array(); } - // Add some defaults + // Add some defaults. $schema['export'] += array( 'key' => 'name', 'key name' => 'Name', @@ -1003,6 +1014,7 @@ function ctools_export_get_schema($table) { 'cache defaults' => FALSE, 'default cache bin' => 'cache', 'export type string' => 'type', + 'boolean' => TRUE, ); // If the export definition doesn't have the "primary key" then the CRUD @@ -1094,7 +1106,7 @@ function ctools_export_set_object_status($object, $new_status = TRUE) { $export = $schema['export']; $status = variable_get($export['status'], array()); - // Compare + // Compare. if (!$new_status && $object->export_type & EXPORT_IN_DATABASE) { unset($status[$object->{$export['key']}]); } @@ -1137,12 +1149,12 @@ function ctools_export_new_object($table, $set_defaults = TRUE) { $schema = ctools_export_get_schema($table); $export = $schema['export']; - $object = new $export['object']; + $object = new $export['object'](); foreach ($schema['fields'] as $field => $info) { if (isset($info['object default'])) { $object->$field = $info['object default']; } - else if (isset($info['default'])) { + elseif (isset($info['default'])) { $object->$field = $info['default']; } else { @@ -1167,11 +1179,11 @@ function ctools_export_new_object($table, $set_defaults = TRUE) { function ctools_export_to_hook_code(&$code, $table, $names = array(), $name = 'foo') { $schema = ctools_export_get_schema($table); $export = $schema['export']; - // Use the schema-specified function for generating hook code, if one exists + // Use the schema-specified function for generating hook code, if one exists. if (function_exists($export['to hook code callback'])) { $output = $export['to hook code callback']($names, $name); } - // Otherwise, the following code generates basic hook code + // Otherwise, the following code generates basic hook code. else { $output = ctools_export_default_to_hook_code($schema, $table, $names, $name); } @@ -1215,7 +1227,7 @@ function ctools_export_default_to_hook_code($schema, $table, $names, $name) { $output .= " \${$export['identifier']}s = array();\n\n"; foreach ($objects as $object) { $output .= ctools_export_crud_export($table, $object, ' '); - $output .= " \${$export['identifier']}s['" . check_plain($object->$export['key']) . "'] = \${$export['identifier']};\n\n"; + $output .= " \${$export['identifier']}s['" . check_plain($object->{$export['key']}) . "'] = \${$export['identifier']};\n\n"; } $output .= " return \${$export['identifier']}s;\n"; $output .= "}\n"; @@ -1223,6 +1235,7 @@ function ctools_export_default_to_hook_code($schema, $table, $names, $name) { return $output; } + /** * Default function for listing bulk exportable objects. */ diff --git a/sites/all/modules/contrib/dev/ctools/includes/fields.inc b/sites/all/modules/contrib/dev/ctools/includes/fields.inc index 162262c..0d3256f 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/fields.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/fields.inc @@ -5,7 +5,6 @@ * Extend core fields with some helper functions to reduce code complexity within views and ctools plugins. */ - /** * Fake an instance of a field. * @@ -75,14 +74,30 @@ function ctools_fields_get_field_formatter_settings_form($field, $formatter_type $conf['formatter_settings'] += $formatter['settings']; } $function = $formatter['module'] . '_field_formatter_settings_form'; + + $instance = ctools_fields_fake_field_instance($field['field_name'], $view_mode, $formatter_type, $conf['formatter_settings']); if (function_exists($function)) { - $instance = ctools_fields_fake_field_instance($field['field_name'], $view_mode, $formatter_type, $conf['formatter_settings']); $settings_form = $function($field, $instance, $view_mode, $form, $form_state); - if ($settings_form) { - $form['ctools_field_list']['#value'][] = $field; - $form += $settings_form; - } } + if (empty($settings_form)) { + $settings_form = array(); + } + + // Allow other modules to alter the formatter settings form. + $context = array( + 'module' => $formatter['module'], + 'formatter' => $formatter, + 'field' => $field, + 'instance' => $instance, + 'view_mode' => $view_mode, + 'form' => $form, + 'form_state' => $form_state, + ); + drupal_alter('field_formatter_settings_form', $settings_form, $context); + + $settings_form['#tree'] = TRUE; + $form['ctools_field_list']['#value'][] = $field; + $form += $settings_form; if (isset($field['cardinality']) && $field['cardinality'] != 1) { list($prefix, $suffix) = explode('@count', t('Skip the first @count item(s)')); @@ -124,7 +139,7 @@ function ctools_fields_get_field_formatter_settings_form($field, $formatter_type */ function ctools_fields_get_field_formatter_info($fields) { $info = array(); - $field_info = module_invoke_all('field_formatter_info'); + $field_info = field_info_formatter_types(); foreach ($fields as $field) { foreach ($field_info as $format_name => $formatter_info) { if (in_array($field['type'], $formatter_info['field types'])) { @@ -132,7 +147,6 @@ function ctools_fields_get_field_formatter_info($fields) { } } } - drupal_alter('field_formatter_info', $info); return $info; } @@ -196,15 +210,15 @@ function ctools_field_label($field_name) { * - Otherwise NULL. * @param $options * An associative array of additional options, with the following keys: - * - 'field_name': The name of the field whose operation should be + * - 'field_name': The name of the field whose operation should be * invoked. By default, the operation is invoked on all the fields * in the entity's bundle. NOTE: This option is not compatible with * the 'deleted' option; the 'field_id' option should be used * instead. - * - 'field_id': The id of the field whose operation should be + * - 'field_id': The id of the field whose operation should be * invoked. By default, the operation is invoked on all the fields * in the entity's' bundles. - * - 'default': A boolean value, specifying which implementation of + * - 'default': A boolean value, specifying which implementation of * the operation should be invoked. * - if FALSE (default), the field types implementation of the operation * will be invoked (hook_field_[op]) @@ -212,10 +226,10 @@ function ctools_field_label($field_name) { * will be invoked (field_default_[op]) * Internal use only. Do not explicitely set to TRUE, but use * _field_invoke_default() instead. - * - 'deleted': If TRUE, the function will operate on deleted fields + * - 'deleted': If TRUE, the function will operate on deleted fields * as well as non-deleted fields. If unset or FALSE, only * non-deleted fields are operated on. - * - 'language': A language code or an array of language codes keyed by field + * - 'language': A language code or an array of language codes keyed by field * name. It will be used to narrow down to a single value the available * languages to act on. * @@ -328,7 +342,7 @@ function ctools_field_foreign_keys($field_name) { $foreign_keys[$field_name] = $field['foreign keys']; } else { - // try to fetch foreign keys from schema, as not everything + // Try to fetch foreign keys from schema, as not everything // stores foreign keys properly in the field info. $module = $field['module']; diff --git a/sites/all/modules/contrib/dev/ctools/includes/jump-menu.inc b/sites/all/modules/contrib/dev/ctools/includes/jump-menu.inc index a5f99aa..e2ae1cf 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/jump-menu.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/jump-menu.inc @@ -8,7 +8,6 @@ * if javascript is in use. Each item is keyed to the href that the button * should go to. With javascript, the page is immediately redirected. Without * javascript, the form is submitted and a drupal_goto() is given. - * */ /** @@ -51,7 +50,7 @@ function ctools_jump_menu($form, &$form_state, $select, $options = array()) { 'hide' => TRUE, ); - ctools_add_js('jump-menu'); + $form['#attached']['js'][] = ctools_attach_js('jump-menu'); if (!empty($options['choose'])) { $select = array('' => $options['choose']) + $select; @@ -127,7 +126,7 @@ function ctools_jump_menu_submit($form, &$form_state) { // This allows duplicate paths to be used in jump menus for multiple options. $redirect_array = explode("::", $form_state['values']['jump']); - if(isset($redirect_array[1]) && !empty($redirect_array[1])){ + if (isset($redirect_array[1]) && !empty($redirect_array[1])) { $redirect = $redirect_array[1]; } else { diff --git a/sites/all/modules/contrib/dev/ctools/includes/language.inc b/sites/all/modules/contrib/dev/ctools/includes/language.inc index 9a7850b..a850670 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/language.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/language.inc @@ -1,5 +1,9 @@ t("Current user's language"), @@ -41,4 +44,4 @@ function ctools_language_list_all() { ); $languages = array_merge($languages, ctools_language_list()); return $languages; -} \ No newline at end of file +} diff --git a/sites/all/modules/contrib/dev/ctools/includes/math-expr.inc b/sites/all/modules/contrib/dev/ctools/includes/math-expr.inc index beffb93..d82281e 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/math-expr.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/math-expr.inc @@ -99,7 +99,10 @@ class ctools_math_expr { 'sqrt','abs','ln','log', 'time', 'ceil', 'floor', 'min', 'max', 'round'); - function ctools_math_expr() { + /** + * ctools_math_expr constructor. + */ + function __construct() { // make the variables a little more accurate $this->v['pi'] = pi(); $this->v['e'] = exp(1); @@ -271,7 +274,7 @@ class ctools_math_expr { } elseif (in_array($op, $ops) and !$expecting_op) { return $this->trigger("unexpected operator '$op'"); } else { // I don't even want to know what you did to get here - return $this->trigger("an unexpected error occured"); + return $this->trigger("an unexpected error occurred"); } if ($index == strlen($expr)) { if (in_array($op, $ops)) { // did we end with an operator? bad. diff --git a/sites/all/modules/contrib/dev/ctools/includes/modal.inc b/sites/all/modules/contrib/dev/ctools/includes/modal.inc index dfde0ac..18b216f 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/modal.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/modal.inc @@ -55,9 +55,9 @@ function ctools_modal_add_js() { 'alt' => t('Close window'), )), 'throbber' => theme('image', array( - 'path' => ctools_image_path('throbber.gif'), - 'title' => t('Loading...'), - 'alt' => t('Loading'), + 'path' => ctools_image_path('throbber.gif'), + 'title' => t('Loading...'), + 'alt' => t('Loading'), )), ), ); @@ -66,6 +66,7 @@ function ctools_modal_add_js() { drupal_add_library('system', 'jquery.form'); drupal_add_library('system', 'drupal.progress'); drupal_add_library('system', 'drupal.ajax'); + drupal_add_library('system', 'ui'); ctools_add_js('modal'); ctools_add_css('modal'); @@ -84,7 +85,7 @@ function ctools_modal_add_plugin_js($plugins) { if (file_exists($file)) { $js[$file] = TRUE; } - else if (file(exists($subtype['path'] . '/' . $file))) { + elseif (file(exists($subtype['path'] . '/' . $file))) { $js[$subtype['path'] . '/' . $file] = TRUE; } } @@ -94,7 +95,7 @@ function ctools_modal_add_plugin_js($plugins) { if (file_exists($file)) { $css[$file] = TRUE; } - else if (file(exists($subtype['path'] . '/' . $file))) { + elseif (file(exists($subtype['path'] . '/' . $file))) { $css[$subtype['path'] . '/' . $file] = TRUE; } } @@ -139,7 +140,7 @@ function ctools_modal_command_dismiss() { } /** - * Display loading screen in the modal + * Display loading screen in the modal. */ function ctools_modal_command_loading() { return array( @@ -187,18 +188,32 @@ function ctools_modal_text_button($text, $dest, $alt, $class = '') { /** * Wrap a form so that we can use it properly with AJAX. Essentially if the * form wishes to render, it automatically does that, otherwise it returns - * so we can see submission results. + * the render array so we can see submission results. * - * @return + * @param array $form + * An associative array containing the structure of the form. + * @param array $form_state + * An associative array containing the current state of the form. + * If the 'reset_html_ids' key is set to TRUE, it will prevent HTML IDs in + * forms from being incremented. + * + * @return mixed * The output of the form, if it was rendered. If $form_state['ajax'] * is set, this will use ctools_modal_form_render so it will be * a $command object suitable for ajax_render already. * - * The return will be NULL if the form was successfully submitted unless - * you specifically set re_render = TRUE. If ajax is set the - * form will never be redirected. + * If the form was not rendered, the raw render array will be returned. + * + * If ajax is set the form will never be redirected. */ function ctools_modal_form_wrapper($form_id, &$form_state) { + // Since this will run again on form rebuild while still in the modal, prevent + // form IDs from being incremented. + // @todo https://drupal.org/node/1305882 + if (!empty($form_state['reset_html_ids']) && !empty($_POST['ajax_html_ids'])) { + unset($_POST['ajax_html_ids']); + } + // This won't override settings already in. $form_state += array( 're_render' => FALSE, @@ -206,8 +221,8 @@ function ctools_modal_form_wrapper($form_id, &$form_state) { ); $output = drupal_build_form($form_id, $form_state); - if (!empty($form_state['ajax']) && (!$form_state['executed'] || $form_state['rebuild'])) { - return ctools_modal_form_render($form_state, $output); + if (!empty($form_state['ajax']) && (!$form_state['executed'] || $form_state['rebuild'])) { + return ctools_modal_form_render($form_state, $output); } return $output; diff --git a/sites/all/modules/contrib/dev/ctools/includes/object-cache.inc b/sites/all/modules/contrib/dev/ctools/includes/object-cache.inc index 29225b0..ef52f9a 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/object-cache.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/object-cache.inc @@ -27,6 +27,7 @@ * defaults to session_id(). * * @deprecated $skip_cache is deprecated in favor of drupal_static* + * * @return * The data that was cached. */ @@ -42,8 +43,11 @@ function ctools_object_cache_get($obj, $name, $skip_cache = FALSE, $sid = NULL) } if (!array_key_exists($key, $cache)) { - $data = db_query('SELECT * FROM {ctools_object_cache} WHERE sid = :session_id AND obj = :object AND name = :name', array(':session_id' => $sid, ':object' => $obj, ':name' => $name)) - ->fetchObject(); + $data = db_query('SELECT * FROM {ctools_object_cache} WHERE sid = :session_id AND obj = :object AND name = :name', array( + ':session_id' => $sid, + ':object' => $obj, + ':name' => md5($name), + ))->fetchObject(); if ($data) { $cache[$key] = unserialize($data->data); } @@ -83,7 +87,7 @@ function ctools_object_cache_set($obj, $name, $cache, $sid = NULL) { ->fields(array( 'sid' => $sid, 'obj' => $obj, - 'name' => $name, + 'name' => md5($name), 'data' => serialize($cache), 'updated' => REQUEST_TIME, )) @@ -91,7 +95,7 @@ function ctools_object_cache_set($obj, $name, $cache, $sid = NULL) { } /** - * Remove an object from the non-volatile ctools cache + * Remove an object from the non-volatile ctools cache. * * @param $obj * A 128 character or less string to define what kind of object is being @@ -103,7 +107,6 @@ function ctools_object_cache_set($obj, $name, $cache, $sid = NULL) { * defaults to session_id(). */ function ctools_object_cache_clear($obj, $name, $sid = NULL) { - if (!$sid) { $sid = session_id(); } @@ -111,13 +114,12 @@ function ctools_object_cache_clear($obj, $name, $sid = NULL) { db_delete('ctools_object_cache') ->condition('sid', $sid) ->condition('obj', $obj) - ->condition('name', $name) + ->condition('name', md5($name)) ->execute(); // Ensure the static cache is emptied of this obj:name set. drupal_static_reset('ctools_object_cache_get'); } - /** * Determine if another user has a given object cached. * @@ -142,8 +144,11 @@ function ctools_object_cache_test($obj, $name, $sid = NULL) { $sid = session_id(); } - return db_query('SELECT s.uid, c.updated FROM {ctools_object_cache} c INNER JOIN {sessions} s ON c.sid = s.sid WHERE s.sid <> :session_id AND c.obj = :obj AND c.name = :name ORDER BY c.updated ASC', array(':session_id' => $sid, ':obj' => $obj, ':name' => $name)) - ->fetchObject(); + return db_query('SELECT s.uid, c.updated FROM {ctools_object_cache} c INNER JOIN {sessions} s ON c.sid = s.sid WHERE s.sid <> :session_id AND c.obj = :obj AND c.name = :name ORDER BY c.updated ASC', array( + ':session_id' => $sid, + ':obj' => $obj, + ':name' => md5($name), + ))->fetchObject(); } /** @@ -162,6 +167,7 @@ function ctools_object_cache_test($obj, $name, $sid = NULL) { * An array of objects containing the UID and updated date for each name found. */ function ctools_object_cache_test_objects($obj, $names) { + array_walk($names, 'md5'); return db_query("SELECT c.name, s.uid, c.updated FROM {ctools_object_cache} c INNER JOIN {sessions} s ON c.sid = s.sid WHERE c.obj = :obj AND c.name IN (:names) ORDER BY c.updated ASC", array(':obj' => $obj, ':names' => $names)) ->fetchAllAssoc('name'); } @@ -180,7 +186,7 @@ function ctools_object_cache_test_objects($obj, $names) { function ctools_object_cache_clear_all($obj, $name) { db_delete('ctools_object_cache') ->condition('obj', $obj) - ->condition('name', $name) + ->condition('name', md5($name)) ->execute(); // Ensure the static cache is emptied of this obj:name set. $cache = &drupal_static('ctools_object_cache_get', array()); @@ -197,7 +203,8 @@ function ctools_object_cache_clear_all($obj, $name) { */ function ctools_object_cache_clean($age = NULL) { if (empty($age)) { - $age = 86400 * 7; // 7 days + // 7 days. + $age = 86400 * 7; } db_delete('ctools_object_cache') ->condition('updated', REQUEST_TIME - $age, '<') diff --git a/sites/all/modules/contrib/dev/ctools/includes/page-wizard.inc b/sites/all/modules/contrib/dev/ctools/includes/page-wizard.inc index a211361..9690aae 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/page-wizard.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/page-wizard.inc @@ -1,5 +1,9 @@ plugin = $plugin; if ($function = ctools_plugin_get_function($plugin, 'default cache')) { $function($cache); diff --git a/sites/all/modules/contrib/dev/ctools/includes/plugins-admin.inc b/sites/all/modules/contrib/dev/ctools/includes/plugins-admin.inc index d4ead0a..c11fcfd 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/plugins-admin.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/plugins-admin.inc @@ -10,6 +10,7 @@ * * Implementing this */ + /** * Get a plugin configuration form. * @@ -100,7 +101,7 @@ function _ctools_plugin_configure_create_form_info(&$form_info, $plugin_definiti if (empty($plugin_definition['title'])) { $title = t('Configure'); } - else if ($op == 'add') { + elseif ($op == 'add') { $title = t('Configure new !plugin_title', array('!plugin_title' => $plugin_definition['title'])); } else { @@ -134,10 +135,10 @@ function _ctools_plugin_configure_create_form_info(&$form_info, $plugin_definiti $form_info['forms']['form']['wrapper'] = 'ctools_plugins_default_form_wrapper'; } } - else if (is_array($info)) { - if (empty($form_info['order'])) { - $form_info['order'] = array(); - } + elseif (is_array($info)) { + if (empty($form_info['order'])) { + $form_info['order'] = array(); + } if (empty($form_info['forms'])) { $form_info['forms'] = array(); } diff --git a/sites/all/modules/contrib/dev/ctools/includes/plugins.inc b/sites/all/modules/contrib/dev/ctools/includes/plugins.inc index 0363fcb..7b53ae1 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/plugins.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/plugins.inc @@ -2,7 +2,6 @@ /** * @file - * * Contains routines to organize and load plugins. It allows a special * variation of the hook system so that plugins can be kept in separate * .inc files, and can be either loaded all at once or loaded only when @@ -70,7 +69,7 @@ function ctools_plugin_api_info($owner, $api, $minimum_version, $current_version if (isset($info['version'])) { $version = $info['version']; } - else if (isset($info['api'])) { + elseif (isset($info['api'])) { $version = $info['api']; } @@ -79,7 +78,9 @@ function ctools_plugin_api_info($owner, $api, $minimum_version, $current_version } // Only process if version is between minimum and current, inclusive. - if (version_compare($version, $minimum_version, '>=') && version_compare($version, $current_version, '<=')) { + if (($version == $minimum_version) || ($version == $current_version) + || (version_compare($version, $minimum_version, '>=') + && version_compare($version, $current_version, '<='))) { if (!isset($info['path'])) { $info['path'] = drupal_get_path('module', $module); } @@ -97,7 +98,7 @@ function ctools_plugin_api_info($owner, $api, $minimum_version, $current_version } // Only process if version is between minimum and current, inclusive. - if (version_compare($info['version'], $minimum_version, '>=') && version_compare($info['version'], $current_version, '<=')) { + if (version_compare($info['version'], $minimum_version, '>=') && version_compare($info['version'], $current_version, '<=')) { if (!isset($info['path'])) { $info['path'] = ''; } @@ -110,7 +111,7 @@ function ctools_plugin_api_info($owner, $api, $minimum_version, $current_version } // Allow other modules to hook in. - drupal_alter($hook, $cache[$owner][$api]); + drupal_alter($hook, $cache[$owner][$api], $owner, $api); } return $cache[$owner][$api]; @@ -149,7 +150,7 @@ function ctools_plugin_api_include($owner, $api, $minimum_version, $current_vers if (isset($plugin_info["$api file"])) { $file = $plugin_info["$api file"]; } - else if (isset($plugin_info['file'])) { + elseif (isset($plugin_info['file'])) { $file = $plugin_info['file']; } else { @@ -159,7 +160,7 @@ function ctools_plugin_api_include($owner, $api, $minimum_version, $current_vers if (file_exists(DRUPAL_ROOT . "/$plugin_info[path]/$file")) { require_once DRUPAL_ROOT . "/$plugin_info[path]/$file"; } - else if (file_exists(DRUPAL_ROOT . "/$file")) { + elseif (file_exists(DRUPAL_ROOT . "/$file")) { require_once DRUPAL_ROOT . "/$file"; } $already_done[$owner][$api][$module] = TRUE; @@ -182,7 +183,7 @@ function ctools_plugin_api_get_hook($owner, $api) { if (function_exists($function = $owner . '_' . $api . '_hook_name')) { $hook = $function(); } - else if (function_exists($function = $owner . '_ctools_plugin_api_hook_name')) { + elseif (function_exists($function = $owner . '_ctools_plugin_api_hook_name')) { $hook = $function(); } @@ -198,29 +199,38 @@ function ctools_plugin_api_get_hook($owner, $api) { /** * Fetch a group of plugins by name. * - * @param $module - * The name of the module that utilizes this plugin system. It will be - * used to call hook_ctools_plugin_$plugin() to get more data about the plugin. - * @param $type + * @param string $module + * The name of the module that utilizes this plugin system. It will be used to + * get more data about the plugin as defined on hook_ctools_plugin_type(). + * @param string $type * The type identifier of the plugin. - * @param $id + * @param string $id * If specified, return only information about plugin with this identifier. * The system will do its utmost to load only plugins with this id. * - * @return - * An array of information arrays about the plugins received. The contents - * of the array are specific to the plugin. + * @return array + * An array of information arrays about the plugins received. The contents of + * the array are specific to the plugin. */ function ctools_get_plugins($module, $type, $id = NULL) { // Store local caches of plugins and plugin info so we don't have to do full - // lookups everytime. - $plugins = &drupal_static('ctools_plugins', array()); + // lookups every time. + static $drupal_static_fast; + if (!isset($drupal_static_fast)) { + $drupal_static_fast['plugins'] = &drupal_static('ctools_plugins', array()); + } + $plugins = &$drupal_static_fast['plugins']; + $info = ctools_plugin_get_plugin_type_info(); - // Bail out noisily if an invalid module/type combination is requested. if (!isset($info[$module][$type])) { - watchdog('ctools', 'Invalid plugin module/type combination requested: module @module and type @type', array('@module' => $module, '@type' => $type), WATCHDOG_ERROR); - return array(); + // If we don't find the plugin we attempt a cache rebuild before bailing out. + $info = ctools_plugin_get_plugin_type_info(TRUE); + // Bail out noisily if an invalid module/type combination is requested. + if (!isset($info[$module][$type])) { + watchdog('ctools', 'Invalid plugin module/type combination requested: module @module and type @type', array('@module' => $module, '@type' => $type), WATCHDOG_ERROR); + return array(); + } } // Make sure our plugins array is populated. @@ -228,8 +238,8 @@ function ctools_get_plugins($module, $type, $id = NULL) { $plugins[$module][$type] = array(); } - // Attempt to shortcut this whole piece of code if we already have - // the requested plugin: + // Attempt to shortcut this whole piece of code if we already have the + // requested plugin: if ($id && array_key_exists($id, $plugins[$module][$type])) { return $plugins[$module][$type][$id]; } @@ -247,9 +257,9 @@ function ctools_get_plugins($module, $type, $id = NULL) { if (!empty($cache->data)) { // Cache load succeeded so use the cached plugin list. - $plugins[$module][$type] = $cache->data; + $plugins[$module][$type] = $cache->data; // Set $setup to true so we know things where loaded. - $setup[$module][$type] = TRUE; + $setup[$module][$type] = TRUE; } else { // Cache load failed so store that we need to build and write the cache. @@ -264,8 +274,8 @@ function ctools_get_plugins($module, $type, $id = NULL) { $plugins[$module][$type] = ctools_plugin_load_hooks($info[$module][$type]); } - // Then see if we should load all files. We only do this if we - // want a list of all plugins or there was a cache miss. + // Then see if we should load all files. We only do this if we want a list of + // all plugins or there was a cache miss. if (empty($setup[$module][$type]) && ($build_cache || !$id)) { $setup[$module][$type] = TRUE; $plugins[$module][$type] = array_merge($plugins[$module][$type], ctools_plugin_load_includes($info[$module][$type])); @@ -288,9 +298,8 @@ function ctools_get_plugins($module, $type, $id = NULL) { } } - - // If we were told earlier that this is cacheable and the cache was - // empty, give something back. + // If we were told earlier that this is cacheable and the cache was empty, + // give something back. if ($build_cache) { cache_set("plugins:$module:$type", $plugins[$module][$type], $info[$module][$type]['cache table']); } @@ -302,7 +311,7 @@ function ctools_get_plugins($module, $type, $id = NULL) { return array_filter($plugins[$module][$type]); } - // Check to see if we need to look for the file + // Check to see if we need to look for the file. if (!array_key_exists($id, $plugins[$module][$type])) { // If we can have child plugins, check to see if the plugin name is in the // format of parent:child and break it up if it is. @@ -343,8 +352,13 @@ function ctools_get_plugins($module, $type, $id = NULL) { * name and each inner array keyed on plugin type name. */ function ctools_plugin_get_plugin_type_info($flush = FALSE) { - $info_loaded = &drupal_static('ctools_plugin_type_info_loaded', FALSE); - $all_type_info = &drupal_static('ctools_plugin_type_info', array()); + static $drupal_static_fast; + if (!isset($drupal_static_fast)) { + $drupal_static_fast['info_loaded'] = &drupal_static('ctools_plugin_type_info_loaded', FALSE); + $drupal_static_fast['all_type_info'] = &drupal_static('ctools_plugin_type_info', array()); + } + $info_loaded = &$drupal_static_fast['info_loaded']; + $all_type_info = &$drupal_static_fast['all_type_info']; // Only trigger info loading once. if ($info_loaded && !$flush) { @@ -403,40 +417,31 @@ function ctools_get_plugins_reset() { /** * Load plugins from a directory. * - * @param $info + * @param array $info * The plugin info as returned by ctools_plugin_get_info() - * @param $file + * @param string $filename * The file to load if we're looking for just one particular plugin. * - * @return - * An array of information created for this plugin. + * @return array + * A (possibly empty) array of information created for this plugin. */ function ctools_plugin_load_includes($info, $filename = NULL) { // Keep a static array so we don't hit file_scan_directory more than necessary. $all_files = &drupal_static(__FUNCTION__, array()); - // store static of plugin arrays for reference because they can't be reincluded. + // Store static of plugin arrays for reference because they can't be + // reincluded, so there is no point in using drupal_static(). static $plugin_arrays = array(); - // If we're being asked for all plugins of a type, skip any caching - // we may have done because this is an admin task and it's ok to - // spend the extra time. - if (!isset($filename)) { - $all_files[$info['module']][$info['type']] = NULL; - } - if (!isset($all_files[$info['module']][$info['type']])) { - // If a filename was set, we will try to load our list of files from - // cache. This is considered normal operation and we try to reduce - // the time spent finding files. - if (isset($filename)) { - $cache = cache_get("ctools_plugin_files:$info[module]:$info[type]"); - if ($cache) { - $all_files[$info['module']][$info['type']] = $cache->data; - } + $cache = cache_get("ctools_plugin_files:$info[module]:$info[type]"); + if ($cache) { + $all_files[$info['module']][$info['type']] = $cache->data; } - - if (!isset($all_files[$info['module']][$info['type']])) { + // Do not attempt any file scan even if the cached entry was empty. + // A NULL entry here would mean the plugin just does not exists, and we + // cannot afford to run file scan on production sites normal run. + elseif (!isset($all_files[$info['module']][$info['type']])) { $all_files[$info['module']][$info['type']] = array(); // Load all our plugins. $directories = ctools_plugin_get_directories($info); @@ -460,24 +465,23 @@ function ctools_plugin_load_includes($info, $filename = NULL) { } foreach ($files as $file) { if (!empty($info['info file'])) { - // Parse a .info file + // Parse a .info file. $result = ctools_plugin_process_info($info, $module, $file); } else { // Parse a hook. - $plugin = NULL; // ensure that we don't have something leftover from earlier. + // Ensure that we don't have something leftover from earlier. + $plugin = NULL; if (isset($plugin_arrays[$file->uri])) { $identifier = $plugin_arrays[$file->uri]; } else { - - require_once DRUPAL_ROOT . '/' . $file->uri; + include_once DRUPAL_ROOT . '/' . $file->uri; // .inc files have a special format for the hook identifier. // For example, 'foo.inc' in the module 'mogul' using the plugin - // whose hook is named 'borg_type' should have a function named (deep breath) - // mogul_foo_borg_type() - + // whose hook is named 'borg_type' should have a function named + // (deep breath) mogul_foo_borg_type(). // If, however, the .inc file set the quasi-global $plugin array, we // can use that and not even call a function. Set the $identifier // appropriately and ctools_plugin_process() will handle it. @@ -490,7 +494,8 @@ function ctools_plugin_load_includes($info, $filename = NULL) { } } - $result = ctools_plugin_process($info, $module, $identifier, dirname($file->uri), basename($file->uri), $file->name); + $result = ctools_plugin_process($info, $module, $identifier, + dirname($file->uri), basename($file->uri), $file->name); } if (is_array($result)) { $plugins = array_merge($plugins, $result); @@ -510,7 +515,7 @@ function ctools_plugin_load_includes($info, $filename = NULL) { * @param $info * The $info array for the plugin as returned by ctools_plugin_get_info(). * - * @return array $directories + * @return array * An array of directories to search. */ function ctools_plugin_get_directories($info) { @@ -536,10 +541,9 @@ function ctools_plugin_get_directories($info) { } /** - * Helper function to build a ctools-friendly list of themes capable of - * providing plugins. + * Helper to build a ctools-friendly list of themes capable of providing plugins. * - * @return array $themes + * @return array * A list of themes that can act as plugin providers, sorted parent-first with * the active theme placed last. */ @@ -550,7 +554,7 @@ function _ctools_list_themes() { $themes = $active = array(); $all_themes = list_themes(); foreach ($all_themes as $name => $theme) { - // Only search from active themes + // Only search from active themes. if (empty($theme->status) && $theme->name != $current) { continue; } @@ -561,19 +565,19 @@ function _ctools_list_themes() { } } - // Construct a parent-first list of all themes + // Construct a parent-first list of all themes. foreach ($active as $name => $theme) { $base_themes = isset($theme->base_themes) ? $theme->base_themes : array(); $themes = array_merge($themes, $base_themes, array($name => $theme->info['name'])); } - // Put the actual theme info objects into the array + // Put the actual theme info objects into the array. foreach (array_keys($themes) as $name) { if (isset($all_themes[$name])) { $themes[$name] = $all_themes[$name]; } } - // Make sure the current default theme always gets the last word + // Make sure the current default theme always gets the last word. if ($current_key = array_search($current, array_keys($themes))) { $themes += array_splice($themes, $current_key, 1); } @@ -581,7 +585,6 @@ function _ctools_list_themes() { return $themes; } - /** * Find all the base themes for the specified theme. * @@ -597,9 +600,10 @@ function _ctools_list_themes() { * The name of the theme whose base we are looking for. * @param $used_keys * A recursion parameter preventing endless loops. - * @return + * + * @return array * Returns an array of all of the theme's ancestors; the first element's value - * will be NULL if an error occurred. + * will be NULL if an error occurred. (Note: this is NOT $arr[0]). */ function ctools_find_base_themes($themes, $key, $used_keys = array()) { $base_key = $themes[$key]->info['base theme']; @@ -627,7 +631,6 @@ function ctools_find_base_themes($themes, $key, $used_keys = array()) { return $current_base_theme; } - /** * Load plugin info for the provided hook; this is handled separately from * plugins from files. @@ -652,22 +655,28 @@ function ctools_plugin_load_hooks($info) { /** * Process a single hook implementation of a ctools plugin. * - * @param $info + * @param array $info * The $info array about the plugin as returned by ctools_plugin_get_info() - * @param $module + * @param string $module * The module that implements the plugin being processed. - * @param $identifier - * The plugin identifier, which is used to create the name of the hook - * function being called. - * @param $path + * @param string|array $identifier + * Used to create the base setting of return value. If: + * - $identifier is a string, a hook name is created from this and the 'hook' + * key of the $info array, and the return value of that hook function is + * used. The hook is called like this: $identifier_$hook($info); + * - $identifier is an array, this array is used directly. + * @param string $path * The path where files utilized by this plugin will be found. - * @param $file + * @param string $file * The file that was loaded for this plugin, if it exists. - * @param $base + * @param string $base * The base plugin name to use. If a file was loaded for the plugin, this * is the plugin to assume must be present. This is used to automatically * translate the array to make the syntax more friendly to plugin * implementors. + * + * @return null|array + * NULL on failure, otherwise an array containing the results keyed by name. */ function ctools_plugin_process($info, $module, $identifier, $path, $file = NULL, $base = NULL) { if (is_array($identifier)) { @@ -678,7 +687,7 @@ function ctools_plugin_process($info, $module, $identifier, $path, $file = NULL, if (!function_exists($function)) { return NULL; } - $result = $function(); + $result = $function($info); if (!isset($result) || !is_array($result)) { return NULL; } @@ -735,9 +744,19 @@ function _ctools_process_data($result, $plugin_type_info, $module, $path, $file) return $result; } - /** * Process an info file for plugin information, rather than a hook. + * + * @param array $info + * The $info array about the plugin as returned by ctools_plugin_get_info() + * @param string $module + * The module that implements the plugin being processed. + * @param object $file + * An object containing 'uri' and 'name' properties. 'uri' is the name of the + * 'info' file to process. 'name' is the plugin key-name. + * + * @return null|array + * NULL on failure, otherwise an array containing the results keyed by name. */ function ctools_plugin_process_info($info, $module, $file) { $result = drupal_parse_info_file($file->uri); @@ -764,7 +783,7 @@ function ctools_plugin_get_info($module, $type) { * @param $function_name * The identifier of the function. For example, 'settings form'. * - * @return + * @return string * The actual name of the function to call, or NULL if the function * does not exist. */ @@ -781,7 +800,7 @@ function ctools_plugin_get_function($plugin_definition, $function_name) { } if (!isset($plugin_definition[$function_name])) { - return; + return NULL; } if (is_array($plugin_definition[$function_name]) && isset($plugin_definition[$function_name]['function'])) { @@ -816,7 +835,7 @@ function ctools_plugin_get_function($plugin_definition, $function_name) { * @param $function_name * The identifier of the function. For example, 'settings form'. * - * @return + * @return string * The actual name of the function to call, or NULL if the function * does not exist. */ @@ -834,7 +853,7 @@ function ctools_plugin_load_function($module, $type, $id, $function_name) { * @param $class_name * The identifier of the class. For example, 'handler'. * - * @return + * @return string * The actual name of the class to call, or NULL if the class does not exist. */ function ctools_plugin_get_class($plugin_definition, $class_name) { @@ -853,11 +872,11 @@ function ctools_plugin_get_class($plugin_definition, $class_name) { if (!isset($plugin_definition[$class_name])) { return; } - else if (is_string($plugin_definition[$class_name])) { + elseif (is_string($plugin_definition[$class_name])) { // Plugin uses the string form shorthand. $return = $plugin_definition[$class_name]; } - else if (isset($plugin_definition[$class_name]['class'])) { + elseif (isset($plugin_definition[$class_name]['class'])) { // Plugin uses the verbose array form. $return = $plugin_definition[$class_name]['class']; } @@ -879,7 +898,7 @@ function ctools_plugin_get_class($plugin_definition, $class_name) { * @param $class_name * The identifier of the class. For example, 'handler'. * - * @return + * @return string * The actual name of the class to call, or NULL if the class does not exist. */ function ctools_plugin_load_class($module, $type, $id, $class_name) { diff --git a/sites/all/modules/contrib/dev/ctools/includes/registry.inc b/sites/all/modules/contrib/dev/ctools/includes/registry.inc index 9d4328e..7e517bb 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/registry.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/registry.inc @@ -2,7 +2,6 @@ /** * @file - * * Registry magic. In a separate file to minimize unnecessary code loading. */ diff --git a/sites/all/modules/contrib/dev/ctools/includes/stylizer.inc b/sites/all/modules/contrib/dev/ctools/includes/stylizer.inc index 9fdc81d..488a7f3 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/stylizer.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/stylizer.inc @@ -202,7 +202,7 @@ function ctools_stylizer_get_settings_name($settings) { } /** - * Get the path where images will be stored for a given style plugin and settings. + * Get the path where images will be stored for a style plugin and settings. * * This function will make sure the path exists. */ @@ -249,7 +249,7 @@ class ctools_stylizer_image_processor { if (is_string($plugin['actions']) && function_exists($plugin['actions'])) { $actions = $plugin['actions']($plugin, $settings); } - else if (is_array($plugin['actions'])) { + elseif (is_array($plugin['actions'])) { $actions = $plugin['actions']; } @@ -318,7 +318,7 @@ class ctools_stylizer_image_processor { function command_load($name, $file) { $this->log("New workspace: $name (from $file)"); if (!file_exists($file)) { - // Try it relative to the plugin + // Try it relative to the plugin. $file = $this->plugin['path'] . '/' . $file; if (!file_exists($file)) { $this->log("Unable to open $file"); @@ -336,7 +336,7 @@ class ctools_stylizer_image_processor { } /** - * Create a new workspace using the properties of an existing workspace + * Create a new workspace using the properties of an existing workspace. */ function command_new_from($name, $workspace) { $this->log("New workspace: $name from existing $workspace"); @@ -372,7 +372,7 @@ class ctools_stylizer_image_processor { function command_merge_from($workspace, $x = 0, $y = 0) { $this->log("Merge from: $workspace ($x, $y)"); if (empty($this->workspaces[$workspace])) { - $this->log("Workspace $name does not exist.", 'error'); + $this->log("Workspace $workspace does not exist.", 'error'); return; } @@ -382,7 +382,7 @@ class ctools_stylizer_image_processor { function command_merge_to($workspace, $x = 0, $y = 0) { $this->log("Merge to: $workspace ($x, $y)"); if (empty($this->workspaces[$workspace])) { - $this->log("Workspace $name does not exist.", 'error'); + $this->log("Workspace $workspace does not exist.", 'error'); return; } @@ -500,7 +500,7 @@ class ctools_stylizer_image_processor { $palette[$luminosity_input]['green'] = $green; $palette[$luminosity_input]['blue'] = $blue; - // Now we complete the palette, first we'll do it tothe black, and then to + // Now we complete the palette, first we'll do it to the black, and then to // the white. // From input to black @@ -822,7 +822,7 @@ function ctools_stylizer_add_plugin_forms(&$form_info, $plugin, $op) { if ($op == 'add' && isset($plugin['add form'])) { $id = $plugin['add form']; } - else if (isset($plugin['edit form'])) { + elseif (isset($plugin['edit form'])) { $id = $plugin['edit form']; } else { diff --git a/sites/all/modules/contrib/dev/ctools/includes/stylizer.theme.inc b/sites/all/modules/contrib/dev/ctools/includes/stylizer.theme.inc index 85346c1..0ceedff 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/stylizer.theme.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/stylizer.theme.inc @@ -25,4 +25,3 @@ function ctools_stylizer_theme(&$theme) { 'file' => 'includes/stylizer.inc', ); } - diff --git a/sites/all/modules/contrib/dev/ctools/includes/utility.inc b/sites/all/modules/contrib/dev/ctools/includes/utility.inc index 9795dd8..3c939a4 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/utility.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/utility.inc @@ -23,22 +23,9 @@ function ctools_passthrough($module, $type, &$items) { require_once DRUPAL_ROOT . '/' . $file->uri; list($tool) = explode('.', $file->name, 2); - $function = $module . '_' . str_replace ('-', '_', $tool) . '_' . str_replace('-', '_', $type); + $function = $module . '_' . str_replace('-', '_', $tool) . '_' . str_replace('-', '_', $type); if (function_exists($function)) { $function($items); } } } - -/** - * Implementation of hook_theme_registry_alter() - */ -function ctools_theme_registry_alter(&$registry) { - // Move this one last last last so it can catch changes made by modules and themes. - $key = array_search('ctools_preprocess_page', $registry['page']['preprocess functions']); - if ($key) { - unset($registry['page']['preprocess functions'][$key]); - } - $registry['page']['preprocess functions'][] = 'ctools_preprocess_page'; -} - diff --git a/sites/all/modules/contrib/dev/ctools/includes/uuid.inc b/sites/all/modules/contrib/dev/ctools/includes/uuid.inc new file mode 100644 index 0000000..f51838b --- /dev/null +++ b/sites/all/modules/contrib/dev/ctools/includes/uuid.inc @@ -0,0 +1,71 @@ + $params) { $form_info['order'][$step_id] = $params['title']; @@ -83,7 +83,7 @@ function ctools_wizard_multistep_form($form_info, $step, &$form_state) { $form_info['cache location'] = 'storage'; } - // If absolutely nothing was set for the cache area to work on + // If absolutely nothing was set for the cache area to work on. if (!isset($form_state[$form_info['cache location']])) { ctools_include('cache'); $form_state[$form_info['cache location']] = ctools_cache_get($form_info['cache mechanism'], $form_info['cache key']); @@ -199,7 +199,7 @@ function ctools_wizard_multistep_form($form_info, $step, &$form_state) { } } } - else if (isset($form_state['ajax next'])) { + elseif (isset($form_state['ajax next'])) { // Clear a few items off the form state so we don't double post: $next = $form_state['ajax next']; unset($form_state['ajax next']); @@ -274,7 +274,7 @@ function ctools_wizard_wrapper($form, &$form_state) { if (!empty($form_info['show trail'])) { ctools_add_css('wizard'); $form['ctools_trail'] = array( - '#markup' => theme(array('ctools_wizard_trail__' . $form_info['id'], 'ctools_wizard_trail'), array('trail' => $trail)), + '#markup' => theme(array('ctools_wizard_trail__' . $form_info['id'], 'ctools_wizard_trail'), array('trail' => $trail, 'form_info' => $form_info)), '#weight' => -1000, ); } @@ -299,7 +299,7 @@ function ctools_wizard_wrapper($form, &$form_state) { '#wizard type' => 'next', '#weight' => -2000, '#limit_validation_errors' => array(), - // hardcode the submit so that it doesn't try to save data. + // Hardcode the submit so that it doesn't try to save data. '#submit' => array('ctools_wizard_submit'), '#attributes' => $button_attributes, ); @@ -325,19 +325,18 @@ function ctools_wizard_wrapper($form, &$form_state) { // end of the form list (i.e, there is a next) then it's "update and return" // to be clear. If this is the end of the path and there is no next, we // call it 'Finish'. - // Even if there is no direct return path (some forms may not want you // leaving in the middle) the final button is always a Finish and it does // whatever the return action is. if (!empty($form_info['show return']) && !empty($form_state['next'])) { $form['buttons']['return'] = array( '#type' => 'submit', - '#value' => $form_info['return text'], + '#value' => $form_info['return text'], '#wizard type' => 'return', '#attributes' => $button_attributes, ); } - else if (empty($form_state['next']) || !empty($form_info['free trail'])) { + elseif (empty($form_state['next']) || !empty($form_info['free trail'])) { $form['buttons']['return'] = array( '#type' => 'submit', '#value' => $form_info['finish text'], @@ -352,7 +351,7 @@ function ctools_wizard_wrapper($form, &$form_state) { '#type' => 'submit', '#value' => $form_info['cancel text'], '#wizard type' => 'cancel', - // hardcode the submit so that it doesn't try to save data. + // Hardcode the submit so that it doesn't try to save data. '#limit_validation_errors' => array(), '#submit' => array('ctools_wizard_submit'), '#attributes' => $button_attributes, @@ -370,7 +369,6 @@ function ctools_wizard_wrapper($form, &$form_state) { // Set up our submit handler after theirs. Since putting something here will // skip Drupal's autodetect, we autodetect for it. - // We make sure ours is after theirs so that they get to change #next if // the want to. $form['#submit'] = array(); @@ -398,7 +396,7 @@ function ctools_wizard_wrapper($form, &$form_state) { $params = array($url, $options); } - $form['#action'] = call_user_func_array('url', $params); + $form['#action'] = call_user_func_array('url', $params); } if (isset($info['wrapper']) && function_exists($info['wrapper'])) { @@ -418,7 +416,7 @@ function ctools_wizard_submit(&$form, &$form_state) { if (isset($form_state['clicked_button']['#wizard type'])) { $type = $form_state['clicked_button']['#wizard type']; - // if AJAX enabled, we proceed slightly differently here. + // If AJAX enabled, we proceed slightly differently here. if (!empty($form_state['ajax'])) { if ($type == 'next') { $form_state['ajax next'] = $form_state['clicked_button']['#next']; @@ -428,7 +426,7 @@ function ctools_wizard_submit(&$form, &$form_state) { if ($type == 'cancel' && isset($form_state['form_info']['cancel path'])) { $form_state['redirect'] = $form_state['form_info']['cancel path']; } - else if ($type == 'next') { + elseif ($type == 'next') { $form_state['redirect'] = ctools_wizard_get_path($form_state['form_info'], $form_state['clicked_button']['#next']); if (!empty($_GET['destination'])) { // We don't want drupal_goto redirect this request @@ -437,10 +435,10 @@ function ctools_wizard_submit(&$form, &$form_state) { unset($_GET['destination']); } } - else if (isset($form_state['form_info']['return path'])) { + elseif (isset($form_state['form_info']['return path'])) { $form_state['redirect'] = $form_state['form_info']['return path']; } - else if ($type == 'finish' && isset($form_state['form_info']['cancel path'])) { + elseif ($type == 'finish' && isset($form_state['form_info']['cancel path'])) { $form_state['redirect'] = $form_state['form_info']['cancel path']; } } @@ -469,10 +467,8 @@ function ctools_wizard_get_path($form_info, $step) { if (!isset($path[1]) || !is_array($path[1])) { $path[1] = array(); } - // Ensure that the query part of options is an array. - $path[1] += array('query' => array()); // Add the destination parameter, if not set already. - $path[1]['query'] += drupal_get_destination(); + $path[1] += drupal_get_destination(); } return $path; @@ -505,15 +501,15 @@ function ctools_wizard_defaults(&$form_info) { } $form_info = $form_info + $defaults; - // set form callbacks if they aren't defined + // Set form callbacks if they aren't defined. foreach ($form_info['forms'] as $step => $params) { - if (!$params['form id']) { - $form_callback = $hook . '_' . $step . '_form'; - $form_info['forms'][$step]['form id'] = $form_callback; + if (empty($params['form id'])) { + $form_callback = $hook . '_' . $step . '_form'; + $form_info['forms'][$step]['form id'] = $form_callback; } } - // set button callbacks + // Set button callbacks. $callbacks = array( 'back callback' => '_back', 'next callback' => '_next', @@ -523,10 +519,10 @@ function ctools_wizard_defaults(&$form_info) { ); foreach ($callbacks as $key => $callback) { - // never overwrite if explicity defined + // Never overwrite if explicity defined. if (empty($form_info[$key])) { $wizard_callback = $hook . $callback; - if (function_exists($wizard_callback)) { + if (function_exists($wizard_callback)) { $form_info[$key] = $wizard_callback; } } diff --git a/sites/all/modules/contrib/dev/ctools/includes/wizard.theme.inc b/sites/all/modules/contrib/dev/ctools/includes/wizard.theme.inc index 304906c..c1a2646 100644 --- a/sites/all/modules/contrib/dev/ctools/includes/wizard.theme.inc +++ b/sites/all/modules/contrib/dev/ctools/includes/wizard.theme.inc @@ -7,19 +7,16 @@ function ctools_wizard_theme(&$theme) { $theme['ctools_wizard_trail'] = array( - 'variables' => array('trail' => NULL), + 'variables' => array('trail' => NULL, 'form_info' => NULL, 'divider' => ' » '), 'file' => 'includes/wizard.theme.inc', ); } /** - * Themable display of the 'breadcrumb' trail to show the order of the - * forms. + * Themable display of the 'breadcrumb' trail to show the order of the forms. */ function theme_ctools_wizard_trail($vars) { - $trail = $vars['trail']; - if (!empty($trail)) { - return '
    ' . implode(' » ', $trail) . '
    '; + if (!empty($vars['trail'])) { + return '
    ' . implode($vars['divider'], $vars['trail']) . '
    '; } } - diff --git a/sites/all/modules/contrib/dev/ctools/js/auto-submit.js b/sites/all/modules/contrib/dev/ctools/js/auto-submit.js index a3e9aa4..b658577 100644 --- a/sites/all/modules/contrib/dev/ctools/js/auto-submit.js +++ b/sites/all/modules/contrib/dev/ctools/js/auto-submit.js @@ -36,9 +36,11 @@ Drupal.behaviors.CToolsAutoSubmit = { attach: function(context) { // 'this' references the form element function triggerSubmit (e) { - var $this = $(this); - if (!$this.hasClass('ctools-ajaxing')) { - $this.find('.ctools-auto-submit-click').click(); + if ($.contains(document.body, this)) { + var $this = $(this); + if (!$this.hasClass('ctools-ajaxing')) { + $this.find('.ctools-auto-submit-click').click(); + } } } diff --git a/sites/all/modules/contrib/dev/ctools/js/collapsible-div.js b/sites/all/modules/contrib/dev/ctools/js/collapsible-div.js index da9bff9..134151c 100644 --- a/sites/all/modules/contrib/dev/ctools/js/collapsible-div.js +++ b/sites/all/modules/contrib/dev/ctools/js/collapsible-div.js @@ -212,6 +212,7 @@ content.slideToggle(100, afterToggle); } + $container.toggleClass('ctools-collapsed'); toggle.toggleClass('ctools-toggle-collapsed'); // If we're supposed to remember the state of this class, do so. diff --git a/sites/all/modules/contrib/dev/ctools/js/dependent.js b/sites/all/modules/contrib/dev/ctools/js/dependent.js index e9e2447..a60fc12 100644 --- a/sites/all/modules/contrib/dev/ctools/js/dependent.js +++ b/sites/all/modules/contrib/dev/ctools/js/dependent.js @@ -40,7 +40,7 @@ Drupal.CTools.dependent.autoAttach = function() { // Clear active bindings and triggers. for (i in Drupal.CTools.dependent.activeTriggers) { - $(Drupal.CTools.dependent.activeTriggers[i]).unbind('change'); + $(Drupal.CTools.dependent.activeTriggers[i]).unbind('change.ctools-dependent'); } Drupal.CTools.dependent.activeTriggers = []; Drupal.CTools.dependent.activeBindings = {}; @@ -97,7 +97,13 @@ else { switch ($(trigger).attr('type')) { case 'checkbox': - var val = $(trigger).attr('checked') ? true : false; + // **This check determines if using a jQuery version 1.7 or newer which requires the use of the prop function instead of the attr function when not called on an attribute + if ($().prop) { + var val = $(trigger).prop('checked') ? true : false; + } + else { + var val = $(trigger).attr('checked') ? true : false; + } if (val) { $(trigger).siblings('label').removeClass('hidden-options').addClass('expanded-options'); @@ -148,34 +154,41 @@ len++; } - var object = $('#' + id + '-wrapper'); - if (!object.size()) { - // Some elements can't use the parent() method or they can - // damage things. They are guaranteed to have wrappers but - // only if dependent.inc provided them. This check prevents - // problems when multiple AJAX calls cause settings to build - // up. - var $original = $('#' + id); - if ($original.is('fieldset') || $original.is('textarea')) { - continue; - } - - object = $('#' + id).parent(); + var $original = $('#' + id); + if ($original.is('fieldset') || $original.is('textarea')) { + continue; } + var object = $original.parent(); + if (Drupal.settings.CTools.dependent[id].type == 'disable') { if (Drupal.settings.CTools.dependent[id].num <= len) { // Show if the element if criteria is matched - object.attr('disabled', false); - object.addClass('dependent-options'); - object.children().attr('disabled', false); + // **This check determines if using a jQuery version 1.7 or newer which requires the use of the prop function instead of the attr function when not called on an attribute + if (typeof $().prop == 'function') { + object.prop('disabled', false); + object.addClass('dependent-options'); + object.children().prop('disabled', false); + } + else { + object.attr('disabled', false); + object.addClass('dependent-options'); + object.children().attr('disabled', false); + } } else { // Otherwise hide. Use css rather than hide() because hide() // does not work if the item is already hidden, for example, // in a collapsed fieldset. - object.attr('disabled', true); - object.children().attr('disabled', true); + // **This check determines if using a jQuery version 1.7 or newer which requires the use of the prop function instead of the attr function when not called on an attribute + if (typeof $().prop == 'function') { + object.prop('disabled', true); + object.children().prop('disabled', true); + } + else { + object.attr('disabled', true); + object.children().attr('disabled', true); + } } } else { @@ -194,7 +207,7 @@ } } - $(trigger_id).change(function() { + $(trigger_id).bind('change.ctools-dependent', function() { // Trigger the internal change function // the attr('id') is used because closures are more confusing changeTrigger(trigger_id, bind_id); @@ -215,7 +228,7 @@ // is a sort of hacked one that's faster but much less flexible. $("select.ctools-master-dependent") .once('ctools-dependent') - .change(function() { + .bind('change.ctools-dependent', function() { var val = $(this).val(); if (val == 'all') { $('.ctools-dependent-all').show(0); @@ -225,7 +238,7 @@ $('.ctools-dependent-' + val).show(0); } }) - .trigger('change'); + .trigger('change.ctools-dependent'); } } })(jQuery); diff --git a/sites/all/modules/contrib/dev/ctools/js/modal.js b/sites/all/modules/contrib/dev/ctools/js/modal.js index 831649f..ec7b02a 100644 --- a/sites/all/modules/contrib/dev/ctools/js/modal.js +++ b/sites/all/modules/contrib/dev/ctools/js/modal.js @@ -48,7 +48,8 @@ modalOptions: { opacity: .55, background: '#fff' - } + }, + modalClass: 'default' }; var settings = {}; @@ -97,8 +98,13 @@ resize(); $('span.modal-title', Drupal.CTools.Modal.modal).html(Drupal.CTools.Modal.currentSettings.loadingText); - Drupal.CTools.Modal.modalContent(Drupal.CTools.Modal.modal, settings.modalOptions, settings.animation, settings.animationSpeed); - $('#modalContent .modal-content').html(Drupal.theme(settings.throbberTheme)); + Drupal.CTools.Modal.modalContent(Drupal.CTools.Modal.modal, settings.modalOptions, settings.animation, settings.animationSpeed, settings.modalClass); + $('#modalContent .modal-content').html(Drupal.theme(settings.throbberTheme)).addClass('ctools-modal-loading'); + + // Position autocomplete results based on the scroll position of the modal. + $('#modalContent .modal-content').delegate('input.form-autocomplete', 'keyup', function() { + $('#autocomplete').css('top', $(this).position().top + $(this).outerHeight() + $(this).offsetParent().filter('#modal-content').scrollTop()); + }); }; /** @@ -115,18 +121,18 @@ */ Drupal.theme.prototype.CToolsModalDialog = function () { var html = '' - html += '
    ' - html += '
    ' // panels-modal-content - html += ' '; - html += ' '; + html += '
    ' + html += '
    ' // panels-modal-content + html += ' '; + html += ' '; html += '
    '; + html += '
    '; return html; } @@ -136,11 +142,11 @@ */ Drupal.theme.prototype.CToolsModalThrobber = function () { var html = ''; - html += '
    -
    \ No newline at end of file + diff --git a/sites/all/modules/contrib/dev/ctools/page_manager/theme/page_manager.theme.inc b/sites/all/modules/contrib/dev/ctools/page_manager/theme/page_manager.theme.inc index 6435d1c..3df55a8 100644 --- a/sites/all/modules/contrib/dev/ctools/page_manager/theme/page_manager.theme.inc +++ b/sites/all/modules/contrib/dev/ctools/page_manager/theme/page_manager.theme.inc @@ -27,10 +27,10 @@ function template_preprocess_page_manager_edit_page(&$vars) { $vars['locked'] = theme('page_manager_lock', array('page' => $page)); $vars['changed'] = theme('page_manager_changed', array('text' => t('Locked'), 'description' => t('This page is being edited by another user and you cannot make changes to it.'))); } - else if (!empty($page->new)) { + elseif (!empty($page->new)) { $vars['changed'] = theme('page_manager_changed', array('text' => t('New'), 'description' => t('This page is newly created and has not yet been saved to the database. It will not be available until you save it.'))); } - else if (!empty($page->changed)) { + elseif (!empty($page->changed)) { $vars['changed'] = theme('page_manager_changed', array('text' => t('Changed'), 'description' => t('This page has been modified, but these modifications are not yet live. While modifying this page, it is locked from modification by other users.'))); } @@ -49,9 +49,9 @@ function template_preprocess_page_manager_edit_page(&$vars) { */ function theme_page_manager_handler_rearrange($vars) { $form = &$vars['form']; - // Assemble the data for a table from everything in $form['handlers'] + // Assemble the data for a table from everything in $form['handlers']. foreach (element_children($form['handlers']) as $id) { - // provide a reference shortcut. + // Provide a reference shortcut. $element = &$form['handlers'][$id]; if (isset($element['title'])) { $row = array(); diff --git a/sites/all/modules/contrib/dev/ctools/plugins/access/book.inc b/sites/all/modules/contrib/dev/ctools/plugins/access/book.inc new file mode 100644 index 0000000..4b4f7ea --- /dev/null +++ b/sites/all/modules/contrib/dev/ctools/plugins/access/book.inc @@ -0,0 +1,94 @@ + t("Book: node is in a book"), + 'description' => t('Control access based upon a node belonging to a book.'), + 'callback' => 'ctools_book_node_ctools_access_check', + 'default' => array('book' => array()), + 'settings form' => 'ctools_book_node_ctools_access_settings', + 'settings form submit' => 'ctools_book_node_ctools_access_settings_submit', + 'summary' => 'ctools_book_node_ctools_access_summary', + 'required context' => new ctools_context_required(t('Node'), 'node'), + ); +} + +/** + * Settings form for the 'by book_node' access plugin. + */ +function ctools_book_node_ctools_access_settings($form, &$form_state, $conf) { + $options = array( + 'any' => t('In any book'), + ); + $books = book_get_books(); + foreach ($books as $bid => $book) { + $options[$bid] = $book['title']; + } + $form['settings']['book'] = array( + '#title' => t('Book'), + '#type' => 'checkboxes', + '#options' => $options, + '#description' => t('Pass only if the node belongs to one of the selected books'), + '#default_value' => $conf['book'], + '#required' => TRUE, + ); + return $form; +} + +/** + * Check for access. + */ +function ctools_book_node_ctools_access_check($conf, $context) { + // As far as I know there should always be a context at this point, but this + // is safe. + if (empty($context) || empty($context->data) || empty($context->data->book)) { + return FALSE; + } + + if ($conf['book']['any']) { + return !empty($context->data->book); + } + + foreach ($conf['book'] as $bid => $value) { + if ($bid == 'any') { + continue; + } + if ($value && ($bid == $context->data->book['bid'])) { + return TRUE; + } + } + + return FALSE; +} + +/** + * Provide a summary description based upon the checked node_languages. + */ +function ctools_book_node_ctools_access_summary($conf, $context) { + if ($conf['book']['any']) { + return t('@identifier belongs to a book', array('@identifier' => $context->identifier)); + } + + $books = array(); + foreach ($conf['book'] as $bid => $value) { + if ($value) { + $node = node_load($bid); + $books[] = $node->title; + } + } + + if (count($books) == 1) { + return t('@identifier belongs to the book "@book"', array('@book' => $books[0], '@identifier' => $context->identifier)); + } + + return t('@identifier belongs in multiple books', array('@identifier' => $context->identifier)); +} diff --git a/sites/all/modules/contrib/dev/ctools/plugins/access/compare_users.inc b/sites/all/modules/contrib/dev/ctools/plugins/access/compare_users.inc index c271ff4..e0a7f1b 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/access/compare_users.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/access/compare_users.inc @@ -2,7 +2,7 @@ /** * @file - * Ctools access plugin to provide access/visiblity if two user contexts are equal. + * CTools access plugin to provide access/visiblity if two user contexts are equal. */ /** @@ -20,12 +20,12 @@ $plugin = array( 'summary' => 'ctools_compare_users_ctools_access_summary', 'required context' => array( new ctools_context_required(t('First User'), 'user'), - new ctools_context_required(t("Second User"), 'user') + new ctools_context_required(t("Second User"), 'user'), ), ); /** - * Settings form for the 'by perm' access plugin + * Settings form for the 'by perm' access plugin. */ function ctools_compare_users_settings($form, &$form_state, $conf) { diff --git a/sites/all/modules/contrib/dev/ctools/plugins/access/context_exists.inc b/sites/all/modules/contrib/dev/ctools/plugins/access/context_exists.inc index aabc62d..e5d2b5e 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/access/context_exists.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/access/context_exists.inc @@ -2,7 +2,7 @@ /** * @file - * Plugin to provide access control/visibility based on existence of a specified context + * Plugin to provide access control/visibility based on existence of a specified context. */ $plugin = array( @@ -16,7 +16,7 @@ $plugin = array( ); /** - * Settings form + * Settings form. */ function ctools_context_exists_ctools_access_settings($form, &$form_state, $conf) { $form['settings']['exists'] = array( @@ -29,17 +29,17 @@ function ctools_context_exists_ctools_access_settings($form, &$form_state, $conf } /** - * Check for access + * Check for access. */ function ctools_context_exists_ctools_access_check($conf, $context) { - // xor returns false if the two bools are the same, and true if they are not. + // Xor returns false if the two bools are the same, and true if they are not. // i.e, if we asked for context_exists and it does, return true. // If we asked for context does not exist and it does, return false. return (empty($context->data) xor !empty($conf['exists'])); } /** - * Provide a summary description based upon the specified context + * Provide a summary description based upon the specified context. */ function ctools_context_exists_ctools_access_summary($conf, $context) { if (!empty($conf['exists'])) { diff --git a/sites/all/modules/contrib/dev/ctools/plugins/access/entity_bundle.inc b/sites/all/modules/contrib/dev/ctools/plugins/access/entity_bundle.inc index e07a048..fce3419 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/access/entity_bundle.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/access/entity_bundle.inc @@ -43,7 +43,7 @@ function ctools_entity_bundle_ctools_access_get_children($plugin, $parent) { } /** - * Settings form for the 'by entity_bundle' access plugin + * Settings form for the 'by entity_bundle' access plugin. */ function ctools_entity_bundle_ctools_access_settings($form, &$form_state, $conf) { $plugin = $form_state['plugin']; @@ -133,4 +133,3 @@ function ctools_entity_bundle_ctools_access_summary($conf, $context, $plugin) { return format_plural(count($names), '@identifier is bundle "@types"', '@identifier bundle is one of "@types"', array('@types' => implode(', ', $names), '@identifier' => $context->identifier)); } - diff --git a/sites/all/modules/contrib/dev/ctools/plugins/access/entity_field_value.inc b/sites/all/modules/contrib/dev/ctools/plugins/access/entity_field_value.inc index e5857d8..60d2c2e 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/access/entity_field_value.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/access/entity_field_value.inc @@ -16,8 +16,8 @@ $plugin = array( 'get child' => 'ctools_entity_field_value_ctools_access_get_child', 'get children' => 'ctools_entity_field_value_ctools_access_get_children', ); -function ctools_entity_field_value_ctools_access_get_child($plugin, $parent, $child) { +function ctools_entity_field_value_ctools_access_get_child($plugin, $parent, $child) { $plugins = &drupal_static(__FUNCTION__, array()); if (empty($plugins[$parent . ':' . $child])) { list($entity_type, $bundle_type, $field_name) = explode(':', $child); @@ -48,8 +48,7 @@ function ctools_entity_field_value_ctools_access_get_children($plugin, $parent) } function _ctools_entity_field_value_ctools_access_get_child($plugin, $parent, $entity_type, $bundle_type, $field_name, $entity = NULL, $bundle = NULL, $field = NULL) { - - // check that the entity, bundle and field arrays have a value. + // Check that the entity, bundle and field arrays have a value. // If not, load theme using machine names. if (empty($entity)) { $entity = entity_get_info($entity_type); @@ -69,14 +68,14 @@ function _ctools_entity_field_value_ctools_access_get_child($plugin, $parent, $e $plugin['description'] = t('Control access by @entity entity bundle.', array('@entity' => $entity_type)); $plugin['name'] = $parent . ':' . $entity_type . ':' . $bundle_type . ':' . $field_name; $plugin['required context'] = new ctools_context_required(t(ucfirst($entity_type)), $entity_type, array( - 'type' => $bundle_type, - )); + 'type' => $bundle_type, + )); return $plugin; } /** - * Settings form for the 'by entity_bundle' access plugin + * Settings form for the 'by entity_bundle' access plugin. */ function ctools_entity_field_value_ctools_access_settings($form, &$form_state, $conf) { $plugin = $form_state['plugin']; @@ -86,39 +85,44 @@ function ctools_entity_field_value_ctools_access_settings($form, &$form_state, $ $instance = $instances[$field_name]; $field = field_info_field_by_id($instance['field_id']); foreach ($field['columns'] as $column => $attributes) { - $columns[] = _field_sql_storage_columnname($field_name, $column); + $columns[$column] = _field_sql_storage_columnname($field_name, $column); } ctools_include('fields'); - $entity = (object)array( + $entity = (object) array( $entity_info['entity keys']['bundle'] => $bundle_type, ); - $langcode = field_valid_language(NULL); - $form['settings'] += (array) ctools_field_invoke_field($instance, 'form', $entity_type, $entity, $form, $form_state, array('default' => TRUE, 'language' => $langcode)); - // weight is really not important once this is populated and will only interfere with the form layout. - foreach (element_children($form['settings']) as $element) { - unset($form['settings'][$element]['#weight']); - } - // Need more logic here to handle compound fields. - foreach ($columns as $column) { - if (isset($conf[$column]) && is_array($conf[$column])) { - foreach ($conf[$column] as $delta => $conf_value) { - if (is_numeric($delta) && is_array($conf_value)) { - $form['settings'][$field_name][LANGUAGE_NONE][$delta]['value']['#default_value'] = $conf_value['value']; + foreach ($columns as $column => $sql_column) { + if (isset($conf[$sql_column])) { + if (is_array($conf[$sql_column])) { + foreach ($conf[$sql_column] as $delta => $conf_value) { + if (is_numeric($delta)) { + if (is_array($conf_value)) { + $entity->{$field_name}[LANGUAGE_NONE][$delta][$column] = $conf_value[$column]; + } + else { + $entity->{$field_name}[LANGUAGE_NONE][$delta][$column] = $conf_value; + } + } } } + else { + $entity->{$field_name}[LANGUAGE_NONE][0][$column] = $conf[$sql_column]; + } } - else { - $form['settings'][$field_name][LANGUAGE_NONE]['#default_value'] = $conf[$column]; - } + } + + $form['#parents'] = array('settings'); + $langcode = field_valid_language(NULL); + $form['settings'] += (array) ctools_field_invoke_field($instance, 'form', $entity_type, $entity, $form, $form_state, array('default' => TRUE, 'language' => $langcode)); + // Weight is really not important once this is populated and will only interfere with the form layout. + foreach (element_children($form['settings']) as $element) { + unset($form['settings'][$element]['#weight']); } return $form; } -/** - * Compress the entity bundles allowed to the minimum. - */ function ctools_entity_field_value_ctools_access_settings_submit($form, &$form_state) { $plugin = $form_state['plugin']; list($parent, $entity_type, $bundle_type, $field_name) = explode(':', $plugin['name']); @@ -128,17 +132,83 @@ function ctools_entity_field_value_ctools_access_settings_submit($form, &$form_s $instance = $instances[$field_name]; $field = field_info_field_by_id($instance['field_id']); foreach ($field['columns'] as $column => $attributes) { - $columns[] = _field_sql_storage_columnname($field_name, $column); + $columns[$column] = _field_sql_storage_columnname($field_name, $column); } + $items = _ctools_entity_field_value_get_proper_form_items($field, $form_state['values']['settings'][$field_name][$langcode], array_keys($columns)); + foreach ($columns as $column => $sql_column) { + $column_items = _ctools_entity_field_value_filter_items_by_column($items, $column); + $form_state['values']['settings'][$sql_column] = $column_items; + } + $form_state['values']['settings'][$field_name][$langcode] = $items; +} + +function _ctools_entity_field_value_get_proper_form_items($field, $form_items, $columns) { + $items = array(); + + // Single value item. + if (!is_array($form_items)) { + foreach ($columns as $column) { + $items[0][$column] = $form_items; + } + return $items; + } + + foreach ($form_items as $delta => $value) { + $item = array(); + if (is_numeric($delta)) { // Array of field values. + if (!is_array($value)) { // Single value in array. + foreach ($columns as $column) { + $item[$column] = $value; + } + } + else { // Value has colums. + foreach ($columns as $column) { + $item[$column] = isset($value[$column]) ? $value[$column] : ''; + } + } + } + $items[] = $item; + } + + // Check if $form_items is an array of columns. + $item = array(); + $has_columns = FALSE; foreach ($columns as $column) { - $form_state['values']['settings'][$column] = $form_state['input']['settings'][$field_name][$langcode]; + if (isset($form_items[$column])) { + $has_columns = TRUE; + $item[$column] = $form_items[$column]; + } + else { + $item[$column] = ''; + } } + if ($has_columns) { + $items[] = $item; + } + + // Remove empty values. + $items = _field_filter_items($field, $items); + return $items; +} + +function _ctools_entity_field_value_filter_items_by_column($items, $column) { + $column_items = array(); + foreach ($items as $delta => $values) { + $column_items[$delta] = isset($values[$column]) ? $values[$column] : ''; + } + return $column_items; } /** * Check for access. */ function ctools_entity_field_value_ctools_access_check($conf, $context, $plugin) { + if ((!is_object($context)) || (empty($context->data))) { + // If the context doesn't exist -- for example, a newly added entity + // reference is used as a pane visibility criteria -- we deny access. + return FALSE; + } + list($parent, $entity_type, $bundle_type, $field_name) = explode(':', $plugin['name']); if ($field_items = field_get_items($entity_type, $context->data, $field_name)) { @@ -160,16 +230,27 @@ function ctools_entity_field_value_ctools_access_check($conf, $context, $plugin) // Check field value. foreach ($field_items as $field_value) { - foreach ($field_value as $field_column => $value) { - // Iterate through config values. - foreach ($conf_value_array as $conf_value) { + // Iterate through config values. + foreach ($conf_value_array as $conf_value) { + $match = FALSE; + foreach ($field_value as $field_column => $value) { // Check access only for stored in config column values. - if (isset($conf_value[$field_column]) && $value == $conf_value[$field_column]) { - return TRUE; + if (isset($conf_value[$field_column])) { + if ($value == $conf_value[$field_column]) { + $match = TRUE; + } + else { + $match = FALSE; + break; + } } } + if ($match) { + return TRUE; + } } } + return FALSE; } } @@ -178,7 +259,7 @@ function ctools_entity_field_value_ctools_access_check($conf, $context, $plugin) function _ctools_entity_field_value_ctools_access_get_conf_field_values($values, $langcode = LANGUAGE_NONE) { if (!is_array($values) || !isset($values[$langcode])) { - return; + return NULL; } $conf_values = array(); @@ -201,41 +282,130 @@ function ctools_entity_field_value_ctools_access_summary($conf, $context, $plugi $entity = (object)array( $entity_info['entity keys']['bundle'] => $bundle_type, ); - $string = ''; $keys = array(); - $values = array(); + $value_keys = array(); + $keyed_elements = array(); foreach ($field['columns'] as $column => $attributes) { $conf_key = _field_sql_storage_columnname($field_name, $column); - if (count($field['columns']) > 1) { - // Add some sort of handling for compound fields - } - else { - if (isset($conf[$conf_key])) { - $entity->{$field_name}[LANGUAGE_NONE][] = array($column => $conf[$conf_key]); + $keyed_elements["@{$column}_value"] = array(); + + if (isset($conf[$conf_key])) { + if (is_array($conf[$conf_key])) { + $i = 0; + foreach ($conf[$conf_key] as $conf_value) { + if (!is_array($conf_value)) { + $entity->{$field_name}[LANGUAGE_NONE][$i][$column] = $conf_value; + $keyed_elements["@{$column}_value"][$i] = array('#markup' => $conf_value); + } + elseif (isset($conf_value[$column])) { + $entity->{$field_name}[LANGUAGE_NONE][$i][$column] = $conf_value[$column]; + $keyed_elements["@{$column}_value"][$i] = array('#markup' => $conf_value[$column]); + } + $i++; + } + } + else { + $entity->{$field_name}[LANGUAGE_NONE][0][$column] = $conf[$conf_key]; + $keyed_elements["@{$column}_value"][0] = array('#markup' => $conf[$conf_key]); } } - $string .= " @{$column} equals @{$column}_value"; - $keys['@' . $column] = $column; - $values["@{$column}_value"] = $conf[$conf_key]; - } - $view_mode = 'full'; - $null = NULL; - $options = array('language' => LANGUAGE_NONE); - ctools_include('fields'); - $display = field_get_display($instance, $view_mode, $entity); - if (isset($display['module'])) { - $display['type'] = 'list_default'; - $function = $display['module'] . '_field_formatter_view'; - $items = isset($entity->{$field_name}[LANGUAGE_NONE]) ? $entity->{$field_name}[LANGUAGE_NONE] : array(); - if (function_exists($function)) { - $elements = $function($entity_type, $entity, $field, $instance, LANGUAGE_NONE, $items, $display); - } - $value_keys = array_keys($values); - foreach ($value_keys as $key => $value) { - $values[$value] = isset($elements[$key]['#markup']) ? $elements[$key]['#markup'] : ''; - } - } - $values = array_merge($keys, $values); - return t($string, $values); -} + $keys['@' . $column] = $column; + $value_keys[] = "@{$column}_value"; + } + $elements = array(); + $items = isset($entity->{$field_name}[LANGUAGE_NONE]) ? $entity->{$field_name}[LANGUAGE_NONE] : array(); + $view_mode = 'full'; + ctools_include('fields'); + $display = field_get_display($instance, $view_mode, $entity); + if (!isset($display['module'])) { + $display['module'] = $field['module']; + } + if (isset($display['module'])) { + // Choose simple formatter for well known cases. + switch ($display['module']) { + case 'text': + $display['type'] = 'text_default'; + break; + + case 'list': + $display['type'] = 'list_default'; + if ($field['type'] == 'list_boolean') { + $allowed_values = list_allowed_values($field, $instance, $entity_type, $entity); + foreach ($items as $item) { + if (isset($allowed_values[$item['value']])) { + if ($allowed_values[$item['value']] == '') { + $display['type'] = 'list_key'; + break; + } + } + else { + $display['type'] = 'list_key'; + } + } + } + break; + + case 'taxonomy': + $display['type'] = 'taxonomy_term_reference_plain'; + break; + + case 'entityreference': + $display['type'] = 'entityreference_label'; + break; + + default: + // Use field instance formatter setting. + break; + } + + $function = $display['module'] . '_field_formatter_view'; + if (function_exists($function)) { + $entity_group = array(0 => $entity); + $item_group = array(0 => $items); + $instance_group = array(0 => $instance); + field_default_prepare_view($entity_type, $entity_group, $field, $instance_group, LANGUAGE_NONE, $item_group, $display); + $elements = $function($entity_type, $entity, $field, $instance, LANGUAGE_NONE, $item_group[0], $display); + } + } + if (count($elements) > 0) { + foreach ($field['columns'] as $column => $attributes) { + if (count($field['columns']) == 1) { + $keyed_elements["@{$column}_value"] = $elements; + } + } + } + $values = array(); + foreach ($value_keys as $key) { + $output = array(); + $elements = $keyed_elements[$key]; + if (is_array($elements)) { + foreach ($elements as $element_key => $element) { + if (is_numeric($element_key)) { + $value_str = strip_tags(drupal_render($element)); + if (strlen($value_str) > 0) { + $output[] = $value_str; + } + } + } + } + else { + $value_str = strip_tags(drupal_render($elements)); + if (strlen($value_str) > 0) { + $output[] = $value_str; + } + } + $value = implode(', ', $output); + if ($value !== '') { + $values[$key] = implode(', ', $output); + } + } + $string = ''; + $value_count = count($values); + foreach ($keys as $key_name => $column) { + if (isset($values[$key_name . '_value'])) { + $string .= ($value_count > 1) ? " @{$column} = @{$column}_value" : "@{$column}_value"; + } + } + return t('@field is set to "!value"', array('@field' => $instance['label'], '!value' => format_string($string, array_merge($keys, $values)))); +} diff --git a/sites/all/modules/contrib/dev/ctools/plugins/access/front.inc b/sites/all/modules/contrib/dev/ctools/plugins/access/front.inc index 1bbc6e0..8718a14 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/access/front.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/access/front.inc @@ -19,7 +19,7 @@ $plugin = array( ); /** - * Settings form for the 'by parent term' access plugin + * Settings form for the 'by parent term' access plugin. */ function ctools_front_ctools_access_settings($form, &$form_state, $conf) { // No additional configuration necessary. diff --git a/sites/all/modules/contrib/dev/ctools/plugins/access/node_access.inc b/sites/all/modules/contrib/dev/ctools/plugins/access/node_access.inc index fcd275d..c153cc5 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/access/node_access.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/access/node_access.inc @@ -24,7 +24,7 @@ $plugin = array( ); /** - * Settings form for the 'by node_access' access plugin + * Settings form for the 'by node_access' access plugin. */ function ctools_node_access_ctools_access_settings($form, &$form_state, $conf) { $form['settings']['type'] = array( @@ -86,4 +86,3 @@ function ctools_node_access_ctools_access_summary($conf, $context) { return t('@user can create nodes of the same type as @node.', $replacement); } } - diff --git a/sites/all/modules/contrib/dev/ctools/plugins/access/node_comment.inc b/sites/all/modules/contrib/dev/ctools/plugins/access/node_comment.inc new file mode 100644 index 0000000..6cebdaf --- /dev/null +++ b/sites/all/modules/contrib/dev/ctools/plugins/access/node_comment.inc @@ -0,0 +1,32 @@ + t("Node: comments are open"), + 'description' => t('Control access by the nodes comment status.'), + 'callback' => 'ctools_node_comment_ctools_access_check', + 'summary' => 'ctools_node_comment_ctools_access_summary', + 'required context' => new ctools_context_required(t('Node'), 'node'), +); + +/** + * Checks for access. + */ +function ctools_node_comment_ctools_access_check($conf, $context) { + return (!empty($context->data) && $context->data->comment == 2); +} + +/** + * Provides a summary description based upon the checked node_status. + */ +function ctools_node_comment_ctools_access_summary($conf, $context) { + return t('Returns true if the nodes comment status is "open".'); +} diff --git a/sites/all/modules/contrib/dev/ctools/plugins/access/node_language.inc b/sites/all/modules/contrib/dev/ctools/plugins/access/node_language.inc index 0fdcfc6..b824331 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/access/node_language.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/access/node_language.inc @@ -23,7 +23,7 @@ if (module_exists('locale')) { } /** - * Settings form for the 'by node_language' access plugin + * Settings form for the 'by node_language' access plugin. */ function ctools_node_language_ctools_access_settings($form, &$form_state, $conf) { $options = array( @@ -111,4 +111,3 @@ function ctools_node_language_ctools_access_summary($conf, $context) { return format_plural(count($names), '@identifier language is "@languages"', '@identifier language is one of "@languages"', array('@languages' => implode(', ', $names), '@identifier' => $context->identifier)); } - diff --git a/sites/all/modules/contrib/dev/ctools/plugins/access/node_status.inc b/sites/all/modules/contrib/dev/ctools/plugins/access/node_status.inc index ad5ba40..137f282 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/access/node_status.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/access/node_status.inc @@ -30,4 +30,3 @@ function ctools_node_status_ctools_access_check($conf, $context) { function ctools_node_status_ctools_access_summary($conf, $context) { return t('Returns true if the nodes status is "published".'); } - diff --git a/sites/all/modules/contrib/dev/ctools/plugins/access/node_type.inc b/sites/all/modules/contrib/dev/ctools/plugins/access/node_type.inc index 23a3845..d89532a 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/access/node_type.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/access/node_type.inc @@ -22,7 +22,7 @@ $plugin = array( ); /** - * Settings form for the 'by node_type' access plugin + * Settings form for the 'by node_type' access plugin. */ function ctools_node_type_ctools_access_settings($form, &$form_state, $conf) { $types = node_type_get_types(); @@ -114,4 +114,3 @@ function ctools_node_type_ctools_access_summary($conf, $context) { return format_plural(count($names), '@identifier is type "@types"', '@identifier type is one of "@types"', array('@types' => implode(', ', $names), '@identifier' => $context->identifier)); } - diff --git a/sites/all/modules/contrib/dev/ctools/plugins/access/path_visibility.inc b/sites/all/modules/contrib/dev/ctools/plugins/access/path_visibility.inc index 60b8612..1612e1c 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/access/path_visibility.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/access/path_visibility.inc @@ -11,12 +11,12 @@ $plugin = array( 'callback' => 'ctools_path_visibility_ctools_access_check', 'settings form' => 'ctools_path_visibility_ctools_access_settings', 'summary' => 'ctools_path_visibility_ctools_access_summary', - 'required context' => new ctools_context_optional(t('Path'), 'string'), + 'required context' => new ctools_context_optional(t('Path'), 'string'), 'default' => array('visibility_setting' => 1, 'paths' => ''), ); /** - * Settings form + * Settings form. */ function ctools_path_visibility_ctools_access_settings($form, &$form_state, $conf) { $form['settings']['note'] = array( diff --git a/sites/all/modules/contrib/dev/ctools/plugins/access/perm.inc b/sites/all/modules/contrib/dev/ctools/plugins/access/perm.inc index 67516fa..cb2bb81 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/access/perm.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/access/perm.inc @@ -20,11 +20,11 @@ $plugin = array( ); /** - * Settings form for the 'by perm' access plugin + * Settings form for the 'by perm' access plugin. */ function ctools_perm_ctools_access_settings($form, &$form_state, $conf) { $perms = array(); - // Get list of permissions + // Get list of permissions. foreach (module_list(FALSE, FALSE, TRUE) as $module) { // By keeping them keyed by module we can use optgroups with the // 'select' type. @@ -70,4 +70,3 @@ function ctools_perm_ctools_access_summary($conf, $context) { $permissions = module_invoke_all('permission'); return t('@identifier has "@perm"', array('@identifier' => $context->identifier, '@perm' => $permissions[$conf['perm']]['title'])); } - diff --git a/sites/all/modules/contrib/dev/ctools/plugins/access/php.inc b/sites/all/modules/contrib/dev/ctools/plugins/access/php.inc index 35da86d..4e9d056 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/access/php.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/access/php.inc @@ -20,7 +20,7 @@ $plugin = array( ); /** - * Settings form for the 'by perm' access plugin + * Settings form for the 'by perm' access plugin. * * @todo Need a way to provide a list of all available contexts to be used by * the eval-ed PHP. @@ -38,7 +38,7 @@ function ctools_php_ctools_access_settings($form, &$form_state, $conf) { '#type' => 'textarea', '#title' => t('PHP Code'), '#default_value' => $conf['php'], - '#description' => t('Access will be granted if the following PHP code returns TRUE. Do not include <?php ?>. Note that executing incorrect PHP-code can break your Drupal site. All contexts will be available in the $contexts variable.'), + '#description' => t('Access will be granted if the following PHP code returns TRUE. Do not include <?php ?>. Note that executing incorrect PHP-code can break your Drupal site. All contexts will be available in the $contexts variable.'), ); if (!user_access('use PHP for settings')) { $form['settings']['php']['#disabled'] = TRUE; diff --git a/sites/all/modules/contrib/dev/ctools/plugins/access/query_string_exists.inc b/sites/all/modules/contrib/dev/ctools/plugins/access/query_string_exists.inc new file mode 100644 index 0000000..fb3b433 --- /dev/null +++ b/sites/all/modules/contrib/dev/ctools/plugins/access/query_string_exists.inc @@ -0,0 +1,44 @@ + t('Query string exists'), + 'description' => t('Control access by whether or not a query string exists.'), + 'callback' => 'ctools_query_string_exists_ctools_access_check', + 'settings form' => 'ctools_query_string_exists_ctools_access_settings', + 'summary' => 'ctools_query_string_exists_ctools_access_summary', + 'defaults' => array('key' => ''), +); + +/** + * Settings form. + */ +function ctools_query_string_exists_ctools_access_settings($form, &$form_state, $config) { + $form['settings']['key'] = array( + '#title' => t('Query string key'), + '#description' => t('Enter the key of the query string.'), + '#type' => 'textfield', + '#required' => TRUE, + '#default_value' => $config['key'], + ); + + return $form; +} + +/** + * Check for access. + */ +function ctools_query_string_exists_ctools_access_check($config, $context) { + return isset($_GET[$config['key']]); +} + +/** + * Provide a summary description. + */ +function ctools_query_string_exists_ctools_access_summary($config, $context) { + return t('@identifier exists', array('@identifier' => $config['key'])); +} diff --git a/sites/all/modules/contrib/dev/ctools/plugins/access/role.inc b/sites/all/modules/contrib/dev/ctools/plugins/access/role.inc index b633254..4d8cbae 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/access/role.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/access/role.inc @@ -21,7 +21,7 @@ $plugin = array( ); /** - * Settings form for the 'by role' access plugin + * Settings form for the 'by role' access plugin. */ function ctools_role_ctools_access_settings($form, &$form_state, $conf) { $form['settings']['rids'] = array( @@ -76,4 +76,3 @@ function ctools_role_ctools_access_summary($conf, $context) { return format_plural(count($names), '@identifier has role "@roles"', '@identifier has one of "@roles"', array('@roles' => implode(', ', $names), '@identifier' => $context->identifier)); } - diff --git a/sites/all/modules/contrib/dev/ctools/plugins/access/site_language.inc b/sites/all/modules/contrib/dev/ctools/plugins/access/site_language.inc index 9ff2f70..5b7b724 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/access/site_language.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/access/site_language.inc @@ -22,7 +22,7 @@ if (module_exists('locale')) { } /** - * Settings form for the 'by site_language' access plugin + * Settings form for the 'by site_language' access plugin. */ function ctools_site_language_ctools_access_settings($form, &$form_state, $conf) { $options = array( @@ -84,4 +84,3 @@ function ctools_site_language_ctools_access_summary($conf, $context) { return format_plural(count($names), 'Site language is "@languages"', 'Site language is one of "@languages"', array('@languages' => implode(', ', $names))); } - diff --git a/sites/all/modules/contrib/dev/ctools/plugins/access/string_equal.inc b/sites/all/modules/contrib/dev/ctools/plugins/access/string_equal.inc index ad1c88d..feb48e9 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/access/string_equal.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/access/string_equal.inc @@ -2,7 +2,7 @@ /** * @file - * Plugin to provide access control/visibility based on specified context string matching user-specified string + * Plugin to provide access control/visibility based on specified context string matching user-specified string. */ $plugin = array( @@ -16,7 +16,7 @@ $plugin = array( ); /** - * Settings form + * Settings form. */ function ctools_string_equal_ctools_access_settings($form, &$form_state, $conf) { $form['settings']['operator'] = array( @@ -47,7 +47,7 @@ function ctools_string_equal_ctools_access_settings($form, &$form_state, $conf) } /** - * Check for access + * Check for access. */ function ctools_string_equal_ctools_access_check($conf, $context) { if (empty($context) || empty($context->data)) { @@ -66,29 +66,34 @@ function ctools_string_equal_ctools_access_check($conf, $context) { switch ($conf['operator']) { case '=': return $string === $value; + case '!=': return $string !== $value; + case 'regex': return preg_match($value, $string); + case '!regex': return !preg_match($value, $string); } } /** - * Provide a summary description based upon the specified context + * Provide a summary description based upon the specified context. */ function ctools_string_equal_ctools_access_summary($conf, $context) { $values = array('@identifier' => $context->identifier, '@value' => $conf['value']); switch ($conf['operator']) { case '=': return t('@identifier is "@value"', $values); + case '!=': return t('@identifier is not "@value"', $values); + case 'regex': return t('@identifier matches "@value"', $values); + case '!regex': return t('@identifier does not match "@value"', $values); } } - diff --git a/sites/all/modules/contrib/dev/ctools/plugins/access/string_length.inc b/sites/all/modules/contrib/dev/ctools/plugins/access/string_length.inc index 2bad6af..3af504b 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/access/string_length.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/access/string_length.inc @@ -57,17 +57,24 @@ function ctools_string_length_ctools_access_check($conf, $context) { switch ($conf['operator']) { case '<': return $length < $conf['length']; + case '<=': return $length <= $conf['length']; - case '==': + + case '=': return $length == $conf['length']; + case '!=': return $length != $conf['length']; + case '>': return $length > $conf['length']; + case '>=': return $length >= $conf['length']; } + // Invalid Operator sent, return FALSE. + return FALSE; } /** diff --git a/sites/all/modules/contrib/dev/ctools/plugins/access/term.inc b/sites/all/modules/contrib/dev/ctools/plugins/access/term.inc index 36e70de..50ec0d9 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/access/term.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/access/term.inc @@ -22,7 +22,7 @@ $plugin = array( ); /** - * Settings form for the 'by term' access plugin + * Settings form for the 'by term' access plugin. */ function ctools_term_ctools_access_settings($form, &$form_state, $conf) { // If no configuration was saved before, set some defaults. @@ -51,7 +51,6 @@ function ctools_term_ctools_access_settings($form, &$form_state, $conf) { // A note: Dependency works strangely on these forms as they have never been // updated to a more modern system so they are not individual forms of their // own like the content types. - $form['settings']['#tree'] = TRUE; // Loop over each of the configured vocabularies. @@ -124,6 +123,8 @@ function ctools_term_ctools_access_summary($conf, $context) { return format_plural(count($terms), '@term can be the term "@terms"', '@term can be one of these terms: @terms', - array('@terms' => implode(', ', $terms), - '@term' => $context->identifier)); + array( + '@terms' => implode(', ', $terms), + '@term' => $context->identifier, + )); } diff --git a/sites/all/modules/contrib/dev/ctools/plugins/access/term_has_parent.inc b/sites/all/modules/contrib/dev/ctools/plugins/access/term_has_parent.inc index a079e92..16f4fbf 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/access/term_has_parent.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/access/term_has_parent.inc @@ -1,4 +1,5 @@ !empty($conf['vid_' . $vid]) ? $conf['vid_' . $vid] : '', '#size' => 10, '#multiple' => TRUE, - //@todo: Remove the following workaround when the following patch is in core. {@see:http://drupal.org/node/1117526} + // @todo: Remove the following workaround when the following patch is in core. {@see:http://drupal.org/node/1117526} '#name' => sprintf("settings[%u][]", $vid), '#attributes' => array('multiple' => 'multiple'), ); @@ -71,7 +71,6 @@ function ctools_term_has_parent_ctools_access_settings($form, &$form_state, $con foreach (taxonomy_get_tree($vocabulary->vid) as $term) { $terms[$term->tid] = str_repeat('-', $term->depth) . ($term->depth ? ' ' : '') . $term->name; } - //$form['settings']['vid_' . $vid]['#type'] = 'select'; $form['settings']['vid_' . $vid]['#type'] = 'checkboxes'; $form['settings']['vid_' . $vid]['#options'] = $terms; unset($terms); @@ -116,37 +115,36 @@ function ctools_term_has_parent_ctools_access_check($conf, $context) { // we'll start looking up the hierarchy from our context term id. $current_term = $context->data->tid; - $term=''; + $term = ''; - // scan up the tree. - while (true) { - // select parent as term_parent to avoid PHP5 complications with the parent keyword - //@todo: Find a way to reduce the number of queries required for really deep hierarchies. - $term = db_query("SELECT parent AS term_parent, tid AS tid FROM {taxonomy_term_hierarchy} th WHERE th.tid = :tid", array(':tid'=>$current_term))->fetchObject(); + // Scan up the tree. + while (TRUE) { + // Select parent as term_parent to avoid PHP5 complications with the parent keyword. + // @todo: Find a way to reduce the number of queries required for really deep hierarchies. + $term = db_query("SELECT parent AS term_parent, tid AS tid FROM {taxonomy_term_hierarchy} th WHERE th.tid = :tid", array(':tid' => $current_term))->fetchObject(); - // if no term is found, get out of the loop + // If no term is found, get out of the loop. if (!$term || empty($term->tid)) { break; } - // check the term selected, if the user asked it to. + // Check the term selected, if the user asked it to. if (!empty($conf['include_self']) && isset($conf['vid_' . $vid][$term->tid])) { return TRUE; } - // did we find the parent TID we were looking for? + // Did we find the parent TID we were looking for? if (isset($conf['vid_' . $vid][$term->tid])) { // YES, we're done! return TRUE; } // Nope, we didn't find it. - // If this is the top of the hierarchy, stop scanning. - if ($term->term_parent==0) { + if ($term->term_parent == 0) { break; } - // update the parent, and keep scanning. + // Update the parent, and keep scanning. $current_term = $term->term_parent; } @@ -157,7 +155,7 @@ function ctools_term_has_parent_ctools_access_check($conf, $context) { * Provide a summary description based upon the checked terms. */ function ctools_term_has_parent_ctools_access_summary($conf, $context) { - $vid = (int)$conf['vid']; + $vid = (int) $conf['vid']; $terms = array(); foreach ($conf['vid_' . $vid] as $tid) { $term = taxonomy_term_load($tid); @@ -167,6 +165,8 @@ function ctools_term_has_parent_ctools_access_summary($conf, $context) { return format_plural(count($terms), '@term can have the parent "@terms"', '@term can have one of these parents: @terms', - array('@terms' => implode(', ', $terms), - '@term' => $context->identifier)); + array( + '@terms' => implode(', ', $terms), + '@term' => $context->identifier, + )); } diff --git a/sites/all/modules/contrib/dev/ctools/plugins/access/term_parent.inc b/sites/all/modules/contrib/dev/ctools/plugins/access/term_parent.inc index acbaf87..27375df 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/access/term_parent.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/access/term_parent.inc @@ -22,7 +22,7 @@ $plugin = array( ); /** - * Settings form for the 'by parent term' access plugin + * Settings form for the 'by parent term' access plugin. */ function ctools_term_parent_ctools_access_settings($form, &$form_state, $conf) { // If no configuration was saved before, set some defaults. diff --git a/sites/all/modules/contrib/dev/ctools/plugins/access/term_vocabulary.inc b/sites/all/modules/contrib/dev/ctools/plugins/access/term_vocabulary.inc index 644600f..8e9a0ca 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/access/term_vocabulary.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/access/term_vocabulary.inc @@ -2,7 +2,7 @@ /** * @file - * Plugin to provide access control based upon term vocabulary + * Plugin to provide access control based upon term vocabulary. */ /** @@ -17,25 +17,31 @@ $plugin = array( 'settings form' => 'ctools_term_vocabulary_ctools_access_settings', 'settings form submit' => 'ctools_term_vocabulary_ctools_access_settings_submit', 'summary' => 'ctools_term_vocabulary_ctools_access_summary', - 'required context' => new ctools_context_required(t('Vocabulary'), array('taxonomy_term', 'terms', 'taxonomy_vocabulary')), + 'required context' => new ctools_context_required(t('Vocabulary'), array( + 'taxonomy_term', + 'terms', + 'taxonomy_vocabulary', + )), ); /** - * Settings form for the 'by term_vocabulary' access plugin + * Settings form for the 'by term_vocabulary' access plugin. */ function ctools_term_vocabulary_ctools_access_settings($form, &$form_state, $conf) { $options = array(); $vocabularies = taxonomy_get_vocabularies(); foreach ($vocabularies as $voc) { - $options[$voc->vid] = check_plain($voc->name); + $options[$voc->machine_name] = check_plain($voc->name); } - $form['settings']['vids'] = array( + _ctools_term_vocabulary_ctools_access_map_vids($conf); + + $form['settings']['machine_name'] = array( '#type' => 'checkboxes', '#title' => t('Vocabularies'), '#options' => $options, '#description' => t('Only the checked vocabularies will be valid.'), - '#default_value' => $conf['vids'], + '#default_value' => $conf['machine_name'], ); return $form; } @@ -44,7 +50,7 @@ function ctools_term_vocabulary_ctools_access_settings($form, &$form_state, $con * Compress the term_vocabularys allowed to the minimum. */ function ctools_term_vocabulary_ctools_access_settings_submit($form, &$form_state) { - $form_state['values']['settings']['vids'] = array_filter($form_state['values']['settings']['vids']); + $form_state['values']['settings']['machine_name'] = array_filter($form_state['values']['settings']['machine_name']); } /** @@ -53,11 +59,13 @@ function ctools_term_vocabulary_ctools_access_settings_submit($form, &$form_stat function ctools_term_vocabulary_ctools_access_check($conf, $context) { // As far as I know there should always be a context at this point, but this // is safe. - if (empty($context) || empty($context->data) || empty($context->data->vid)) { + if (empty($context) || empty($context->data) || empty($context->data->vocabulary_machine_name)) { return FALSE; } - if (array_filter($conf['vids']) && empty($conf['vids'][$context->data->vid])) { + _ctools_term_vocabulary_ctools_access_map_vids($conf); + + if (array_filter($conf['machine_name']) && empty($conf['machine_name'][$context->data->vocabulary_machine_name])) { return FALSE; } @@ -73,15 +81,45 @@ function ctools_term_vocabulary_ctools_access_summary($conf, $context) { } $vocabularies = taxonomy_get_vocabularies(); + _ctools_term_vocabulary_ctools_access_map_vids($conf); + $names = array(); - foreach (array_filter($conf['vids']) as $vid) { - $names[] = check_plain($vocabularies[$vid]->name); + if (!empty($conf['machine_name'])) { + foreach (array_filter($conf['machine_name']) as $machine_name) { + foreach ($vocabularies as $vocabulary) { + if ($vocabulary->machine_name === $machine_name) { + $names[] = check_plain($vocabulary->name); + continue; + } + } + } } if (empty($names)) { return t('@identifier is any vocabulary', array('@identifier' => $context->identifier)); } - return format_plural(count($names), '@identifier vocabulary is "@vids"', '@identifier vocabulary is one of "@vids"', array('@vids' => implode(', ', $names), '@identifier' => $context->identifier)); + return format_plural(count($names), '@identifier vocabulary is "@machine_names"', '@identifier vocabulary is one of "@machine_names"', array( + '@machine_names' => implode(', ', $names), + '@identifier' => $context->identifier, + )); } +/** + * Helper function to map the vids from old features to the new machine_name. + * + * Add the machine_name key to $conf if the vids key exist. + * + * @param array $conf + * The configuration of this plugin. + */ +function _ctools_term_vocabulary_ctools_access_map_vids(&$conf) { + if (!empty($conf['vids'])) { + $conf['machine_name'] = array(); + $vocabularies = taxonomy_get_vocabularies(); + foreach ($conf['vids'] as $vid) { + $machine_name = $vocabularies[$vid]->machine_name; + $conf['machine_name'][$machine_name] = $vocabularies[$vid]->machine_name; + } + } +} diff --git a/sites/all/modules/contrib/dev/ctools/plugins/access/theme.inc b/sites/all/modules/contrib/dev/ctools/plugins/access/theme.inc index 4f4be6d..67e3e6f 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/access/theme.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/access/theme.inc @@ -19,7 +19,7 @@ $plugin = array( ); /** - * Settings form for the 'by theme' access plugin + * Settings form for the 'by theme' access plugin. */ function ctools_theme_ctools_access_settings($form, &$form_state, $conf) { $themes = array(); @@ -44,10 +44,10 @@ function ctools_theme_ctools_access_check($conf, $context) { if (!empty($GLOBALS['theme'])) { $theme = $GLOBALS['theme']; } - else if (!empty($GLOBALS['custom_theme'])) { + elseif (!empty($GLOBALS['custom_theme'])) { $theme = $GLOBALS['custom_theme']; } - else if (!empty($GLOBALS['user']->theme)) { + elseif (!empty($GLOBALS['user']->theme)) { $theme = $GLOBALS['user']->theme; } else { diff --git a/sites/all/modules/contrib/dev/ctools/plugins/arguments/entity_id.inc b/sites/all/modules/contrib/dev/ctools/plugins/arguments/entity_id.inc index 451d444..73bfe1e 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/arguments/entity_id.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/arguments/entity_id.inc @@ -2,8 +2,7 @@ /** * @file - * - * Plugin to provide an argument handler for all entity ids + * Plugin to provide an argument handler for all entity ids. */ /** @@ -16,6 +15,10 @@ $plugin = array( 'context' => 'ctools_argument_entity_id_context', 'get child' => 'ctools_argument_entity_id_get_child', 'get children' => 'ctools_argument_entity_id_get_children', + 'default' => array( + 'entity_id' => '', + ), + 'placeholder form' => 'ctools_argument_entity_id_ctools_argument_placeholder', ); function ctools_argument_entity_id_get_child($plugin, $parent, $child) { @@ -37,6 +40,7 @@ function ctools_argument_entity_id_get_children($original_plugin, $parent) { $plugins[$plugin_id] = $plugin; } drupal_alter('ctools_entity_contexts', $plugins); + return $plugins; } @@ -56,15 +60,87 @@ function ctools_argument_entity_id_context($arg = NULL, $conf = NULL, $empty = F return ctools_context_create('entity:' . $entity_type, $arg); } + // Trim spaces and other garbage. + $arg = trim($arg); + if (!is_numeric($arg)) { + $preg_matches = array(); + $match = preg_match('/\[id: (\d+)\]/', $arg, $preg_matches); + if (!$match) { + $match = preg_match('/^id: (\d+)/', $arg, $preg_matches); + } + + if ($match) { + $id = $preg_matches[1]; + } + if (isset($id) && is_numeric($id)) { + return ctools_context_create('entity:' . $entity_type, $id); + } return FALSE; } - $entity = entity_load($entity_type, array($arg)); - if (!$entity) { + $entities = entity_load($entity_type, array($arg)); + if (empty($entities)) { return FALSE; } - return ctools_context_create('entity:' . $entity_type, $entity[$arg]); + return ctools_context_create('entity:' . $entity_type, reset($entities)); } +function ctools_argument_entity_id_settings_form(&$form, &$form_state, $conf) { + $plugin = &$form_state['plugin']; + + $form['settings']['entity'] = array( + '#title' => t('Enter the title or ID of a @entity entity', array('@entity' => $plugin['keyword'])), + '#type' => 'textfield', + '#maxlength' => 512, + '#autocomplete_path' => 'ctools/autocomplete/' . $plugin['keyword'], + '#weight' => -10, + ); + + if (!empty($conf['entity_id'])) { + $info = entity_load($plugin['keyword'], array($conf['entity_id'])); + $info = $info[$conf['entity_id']]; + if ($info) { + $entity = entity_get_info($plugin['keyword']); + $uri = entity_uri($plugin['keyword'], $info); + if (is_array($uri) && $entity['entity keys']['label']) { + $link = l(t("'%title' [%type id %id]", array('%title' => $info->{$entity['entity keys']['label']}, '%type' => $plugin['keyword'], '%id' => $conf['entity_id'])), $uri['path'], array('attributes' => array('target' => '_blank', 'title' => t('Open in new window')), 'html' => TRUE)); + } + elseif (is_array($uri)) { + $link = l(t("[%type id %id]", array('%type' => $plugin['keyword'], '%id' => $conf['entity_id'])), $uri['path'], array('attributes' => array('target' => '_blank', 'title' => t('Open in new window')), 'html' => TRUE)); + } + elseif ($entity['entity keys']['label']) { + $link = l(t("'%title' [%type id %id]", array('%title' => $info->{$entity['entity keys']['label']}, '%type' => $plugin['keyword'], '%id' => $conf['entity_id'])), file_create_url($uri), array('attributes' => array('target' => '_blank', 'title' => t('Open in new window')), 'html' => TRUE)); + } + else { + $link = t("[%type id %id]", array('%type' => $plugin['keyword'], '%id' => $conf['entity_id'])); + } + $form['settings']['entity']['#description'] = t('Currently set to !link', array('!link' => $link)); + } + } + + $form['settings']['entity_id'] = array( + '#type' => 'value', + '#value' => isset($conf['entity_id']) ? $conf['entity_id'] : '', + ); + + $form['settings']['entity_type'] = array( + '#type' => 'value', + '#value' => $plugin['keyword'], + ); + + return $form; +} + +function ctools_argument_entity_id_ctools_argument_placeholder($conf) { + $conf = array( + '#title' => t('Enter the title or ID of a @entity entity', array('@entity' => $conf['keyword'])), + '#type' => 'textfield', + '#maxlength' => 512, + '#autocomplete_path' => 'ctools/autocomplete/' . $conf['keyword'], + '#weight' => -10, + ); + + return $conf; +} diff --git a/sites/all/modules/contrib/dev/ctools/plugins/arguments/nid.inc b/sites/all/modules/contrib/dev/ctools/plugins/arguments/nid.inc index 9aaec0e..cbb56d3 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/arguments/nid.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/arguments/nid.inc @@ -2,8 +2,7 @@ /** * @file - * - * Plugin to provide an argument handler for a node id + * Plugin to provide an argument handler for a node id. */ /** @@ -47,4 +46,3 @@ function ctools_argument_nid_context($arg = NULL, $conf = NULL, $empty = FALSE) return ctools_context_create('node', $node); } - diff --git a/sites/all/modules/contrib/dev/ctools/plugins/arguments/node_add.inc b/sites/all/modules/contrib/dev/ctools/plugins/arguments/node_add.inc index c811311..f249a94 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/arguments/node_add.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/arguments/node_add.inc @@ -2,8 +2,7 @@ /** * @file - * - * Plugin to provide an argument handler for a Node add form + * Plugin to provide an argument handler for a Node add form. */ /** @@ -12,7 +11,7 @@ */ $plugin = array( 'title' => t("Node add form: node type"), - // keyword to use for %substitution + // Keyword to use for %substitution. 'keyword' => 'node_type', 'description' => t('Creates a node add form context from a node type argument.'), 'context' => 'ctools_node_add_context', @@ -29,4 +28,3 @@ function ctools_node_add_context($arg = NULL, $conf = NULL, $empty = FALSE) { return ctools_context_create('node_add_form', $arg); } - diff --git a/sites/all/modules/contrib/dev/ctools/plugins/arguments/node_edit.inc b/sites/all/modules/contrib/dev/ctools/plugins/arguments/node_edit.inc index c7cdf29..8bd0768 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/arguments/node_edit.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/arguments/node_edit.inc @@ -2,8 +2,7 @@ /** * @file - * - * Plugin to provide an argument handler for a Node edit form + * Plugin to provide an argument handler for a Node edit form. */ /** @@ -12,7 +11,7 @@ */ $plugin = array( 'title' => t("Node edit form: node ID"), - // keyword to use for %substitution + // Keyword to use for %substitution. 'keyword' => 'node', 'description' => t('Creates a node edit form context from a node ID argument.'), 'context' => 'ctools_node_edit_context', @@ -48,4 +47,3 @@ function ctools_node_edit_context($arg = NULL, $conf = NULL, $empty = FALSE) { // This will perform a node_access check, so we don't have to. return ctools_context_create('node_edit_form', $node); } - diff --git a/sites/all/modules/contrib/dev/ctools/plugins/arguments/rid.inc b/sites/all/modules/contrib/dev/ctools/plugins/arguments/rid.inc index dd30848..d0cc30c 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/arguments/rid.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/arguments/rid.inc @@ -2,8 +2,7 @@ /** * @file - * - * Plugin to provide an argument handler for a node revision id + * Plugin to provide an argument handler for a node revision id. */ /** @@ -39,7 +38,7 @@ function ctools_argument_rid_context($arg = NULL, $conf = NULL, $empty = FALSE) return FALSE; } - $nid = db_query('SELECT nid FROM {node_revisions} WHERE vid = :vid', array(':vid' => $arg))->fetchField(); + $nid = db_query('SELECT nid FROM {node_revision} WHERE vid = :vid', array(':vid' => $arg))->fetchField(); $node = node_load($nid, $arg); if (!$node) { return FALSE; @@ -47,4 +46,3 @@ function ctools_argument_rid_context($arg = NULL, $conf = NULL, $empty = FALSE) return ctools_context_create('node', $node); } - diff --git a/sites/all/modules/contrib/dev/ctools/plugins/arguments/string.inc b/sites/all/modules/contrib/dev/ctools/plugins/arguments/string.inc index ed4ffbb..005e40e 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/arguments/string.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/arguments/string.inc @@ -2,16 +2,16 @@ /** * @file - * - * Plugin to provide an argument handler for a raw string + * Plugin to provide an argument handler for a raw string. */ + /** * Plugins are described by creating a $plugin array which will be used * by the system that includes this file. */ $plugin = array( 'title' => t("String"), - // keyword to use for %substitution + // Keyword to use for %substitution. 'keyword' => 'string', 'description' => t('A string is a minimal context that simply holds a string that can be used for some other purpose.'), 'settings form' => 'ctools_string_settings_form', @@ -20,7 +20,8 @@ $plugin = array( '#type' => 'textfield', '#description' => t('Enter a value for this argument'), ), - 'path placeholder' => 'ctools_string_path_placeholder', // This is in pagemanager. +// This is in pagemanager. + 'path placeholder' => 'ctools_string_path_placeholder', ); /** @@ -39,7 +40,7 @@ function ctools_string_context($arg = NULL, $conf = NULL, $empty = FALSE) { } /** - * Settings form for the argument + * Settings form for the argument. */ function ctools_string_settings_form(&$form, &$form_state, $conf) { $form['settings']['use_tail'] = array( @@ -48,7 +49,6 @@ function ctools_string_settings_form(&$form, &$form_state, $conf) { '#default_value' => !empty($conf['use_tail']), '#description' => t('If checked, this string will include all arguments. For example, if the path is "path/%" and the user visits "path/foo/bar", if this is not checked the string will be "foo". If it is checked the string will be "foo/bar".'), ); -// return $form; } /** @@ -61,4 +61,4 @@ function ctools_string_path_placeholder($argument) { else { return '%pm_arg_tail'; } -} \ No newline at end of file +} diff --git a/sites/all/modules/contrib/dev/ctools/plugins/arguments/term.inc b/sites/all/modules/contrib/dev/ctools/plugins/arguments/term.inc index 868c8aa..f72e93d 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/arguments/term.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/arguments/term.inc @@ -2,8 +2,7 @@ /** * @file - * - * Plugin to provide an argument handler for a Taxonomy term + * Plugin to provide an argument handler for a Taxonomy term. */ /** @@ -12,12 +11,13 @@ */ $plugin = array( 'title' => t("Taxonomy term: ID"), - // keyword to use for %substitution + // Keyword to use for %substitution. 'keyword' => 'term', 'description' => t('Creates a single taxonomy term from a taxonomy ID or taxonomy term name.'), 'context' => 'ctools_term_context', 'default' => array('input_form' => 'tid', 'breadcrumb' => TRUE, 'transform' => FALSE), 'settings form' => 'ctools_term_settings_form', + 'settings form validate' => 'ctools_term_settings_form_validate', 'placeholder form' => 'ctools_term_ctools_argument_placeholder', 'breadcrumb' => 'ctools_term_breadcrumb', ); @@ -31,6 +31,16 @@ function ctools_term_context($arg = NULL, $conf = NULL, $empty = FALSE) { return ctools_context_create_empty('entity:taxonomy_term'); } + if (isset($conf['vocabularies'])) { + $vocabularies = $conf['vocabularies']; + } + else { + $vids = isset($conf['vids']) ? $conf['vids'] : array(); + + // Convert legacy use of vids to machine names. + $vocabularies = _ctools_term_vocabulary_machine_name_convert($vids); + } + if (is_object($arg)) { $term = $arg; } @@ -50,14 +60,13 @@ function ctools_term_context($arg = NULL, $conf = NULL, $empty = FALSE) { } $terms = taxonomy_get_term_by_name($arg); - - $conf['vids'] = is_array($conf['vids']) ? array_filter($conf['vids']) : NULL; - if ((count($terms) > 1) && isset($conf['vids'])) { + // If only one term is found, fall through to vocabulary check below. + if ((count($terms) > 1) && $vocabularies) { foreach ($terms as $potential) { - foreach ($conf['vids'] as $vid => $active) { - if ($active && $potential->vid == $vid) { + foreach ($vocabularies as $machine_name) { + if ($potential->vocabulary_machine_name == $machine_name) { $term = $potential; - // break out of the foreaches AND the case + // Break out of the foreaches AND the case. break 3; } } @@ -72,7 +81,7 @@ function ctools_term_context($arg = NULL, $conf = NULL, $empty = FALSE) { } } - if (!empty($conf['vids']) && array_filter($conf['vids']) && empty($conf['vids'][$term->vid])) { + if ($vocabularies && !isset($vocabularies[$term->vocabulary_machine_name])) { return NULL; } @@ -82,7 +91,7 @@ function ctools_term_context($arg = NULL, $conf = NULL, $empty = FALSE) { } /** - * Settings form for the argument + * Settings form for the argument. */ function ctools_term_settings_form(&$form, &$form_state, $conf) { // @todo allow synonym use like Views does. @@ -98,13 +107,20 @@ function ctools_term_settings_form(&$form, &$form_state, $conf) { $vocabularies = taxonomy_get_vocabularies(); $options = array(); foreach ($vocabularies as $vid => $vocab) { - $options[$vid] = $vocab->name; + $options[$vocab->machine_name] = $vocab->name; } - $form['settings']['vids'] = array( + + // Fallback on legacy 'vids', when no vocabularies are available. + if (empty($conf['vocabularies']) && !empty($conf['vids'])) { + $conf['vocabularies'] = _ctools_term_vocabulary_machine_name_convert(array_filter($conf['vids'])); + unset($conf['vids']); + } + + $form['settings']['vocabularies'] = array( '#title' => t('Limit to these vocabularies'), '#type' => 'checkboxes', '#options' => $options, - '#default_value' => !empty($conf['vids']) ? $conf['vids'] : array(), + '#default_value' => !empty($conf['vocabularies']) ? $conf['vocabularies'] : array(), '#description' => t('If no vocabularies are checked, terms from all vocabularies will be accepted.'), ); @@ -120,7 +136,12 @@ function ctools_term_settings_form(&$form, &$form_state, $conf) { '#type' => 'checkbox', '#default_value' => !empty($conf['transform']), ); -// return $form; +} + +function ctools_term_settings_form_validate(&$form, &$form_state) { + // Filter the selected vocabularies to avoid storing redundant data. + $vocabularies = array_filter($form_state['values']['settings']['vocabularies']); + form_set_value($form['settings']['vocabularies'], $vocabularies, $form_state); } /** @@ -134,6 +155,7 @@ function ctools_term_ctools_argument_placeholder($conf) { '#type' => 'textfield', '#description' => t('Enter a taxonomy term ID.'), ); + case 'term': return array( '#type' => 'textfield', @@ -161,3 +183,21 @@ function ctools_term_breadcrumb($conf, $context) { $breadcrumb = array_merge(drupal_get_breadcrumb(), array_reverse($breadcrumb)); drupal_set_breadcrumb($breadcrumb); } + +/** + * Helper function to convert convert legacy vocabulary ids into machine names. + * + * @param array $vids + * Array of either vids. + * + * @return array + * A keyed array of machine names. + */ +function _ctools_term_vocabulary_machine_name_convert($vids) { + $vocabularies = taxonomy_vocabulary_load_multiple($vids); + $return = array(); + foreach ($vocabularies as $vocabulary) { + $return[$vocabulary->machine_name] = $vocabulary->machine_name; + } + return $return; +} diff --git a/sites/all/modules/contrib/dev/ctools/plugins/arguments/terms.inc b/sites/all/modules/contrib/dev/ctools/plugins/arguments/terms.inc index 4298ea9..b816e1c 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/arguments/terms.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/arguments/terms.inc @@ -2,8 +2,7 @@ /** * @file - * - * Plugin to provide an argument handler for a Taxonomy term + * Plugin to provide an argument handler for a Taxonomy term. */ /** @@ -12,7 +11,7 @@ */ $plugin = array( 'title' => t("Taxonomy term (multiple): ID"), - // keyword to use for %substitution + // Keyword to use for %substitution. 'keyword' => 'term', 'description' => t('Creates a group of taxonomy terms from a list of tids separated by a comma or a plus sign. In general the first term of the list will be used for panes.'), 'context' => 'ctools_terms_context', @@ -45,7 +44,7 @@ function ctools_terms_context($arg = NULL, $conf = NULL, $empty = FALSE) { } /** - * Settings form for the argument + * Settings form for the argument. */ function ctools_terms_settings_form(&$form, &$form_state, $conf) { $form['settings']['breadcrumb'] = array( @@ -54,7 +53,6 @@ function ctools_terms_settings_form(&$form, &$form_state, $conf) { '#default_value' => !empty($conf['breadcrumb']), '#description' => t('If checked, taxonomy term parents will appear in the breadcrumb trail.'), ); -// return $form; } /** @@ -65,6 +63,7 @@ function ctools_terms_breadcrumb($conf, $context) { return; } + $current = new stdClass(); $current->tid = $context->tids[0]; $breadcrumb = array(); while ($parents = taxonomy_get_parents($current->tid)) { diff --git a/sites/all/modules/contrib/dev/ctools/plugins/arguments/uid.inc b/sites/all/modules/contrib/dev/ctools/plugins/arguments/uid.inc index f9d5315..c6ca3cc 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/arguments/uid.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/arguments/uid.inc @@ -2,8 +2,7 @@ /** * @file - * - * Plugin to provide an argument handler for a user id + * Plugin to provide an argument handler for a user id. */ /** @@ -12,7 +11,7 @@ */ $plugin = array( 'title' => t("User: ID"), - // keyword to use for %substitution + // Keyword to use for %substitution. 'keyword' => 'user', 'description' => t('Creates a user context from a user ID argument.'), 'context' => 'ctools_argument_uid_context', @@ -21,7 +20,8 @@ $plugin = array( '#description' => t('Enter the user ID of a user for this argument'), ), 'default' => array('to_arg' => TRUE), - 'path placeholder' => '%pm_uid_arg', // This is in pagemanager. +// This is in pagemanager. + 'path placeholder' => '%pm_uid_arg', 'path placeholder to_arg' => TRUE, 'no ui' => TRUE, ); diff --git a/sites/all/modules/contrib/dev/ctools/plugins/arguments/user_edit.inc b/sites/all/modules/contrib/dev/ctools/plugins/arguments/user_edit.inc index 32b2b81..9c52b58 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/arguments/user_edit.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/arguments/user_edit.inc @@ -2,8 +2,7 @@ /** * @file - * - * Plugin to provide an argument handler for a Taxonomy term + * Plugin to provide an argument handler for a Taxonomy term. */ /** @@ -12,7 +11,7 @@ */ $plugin = array( 'title' => t("User edit form: User ID"), - // keyword to use for %substitution + // Keyword to use for %substitution. 'keyword' => 'user', 'description' => t('Creates a user edit form context from a user ID argument.'), 'context' => 'ctools_user_edit_context', @@ -30,18 +29,18 @@ function ctools_user_edit_context($arg = NULL, $conf = NULL, $empty = FALSE) { if ($empty) { return ctools_context_create_empty('user_edit_form'); } - if(is_object($arg)){ + if (is_object($arg)) { return ctools_context_create('user_edit_form', $arg); } if (!is_numeric($arg)) { return FALSE; } - $account= user_load($arg); + $account = user_load($arg); if (!$account) { return NULL; } // This will perform a node_access check, so we don't have to. return ctools_context_create('user_edit_form', $account); -} \ No newline at end of file +} diff --git a/sites/all/modules/contrib/dev/ctools/plugins/arguments/user_name.inc b/sites/all/modules/contrib/dev/ctools/plugins/arguments/user_name.inc index f6f3b46..ed74d9d 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/arguments/user_name.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/arguments/user_name.inc @@ -2,8 +2,7 @@ /** * @file - * - * Plugin to provide an argument handler for a username + * Plugin to provide an argument handler for a username. */ /** @@ -12,7 +11,7 @@ */ $plugin = array( 'title' => t("User: name"), - // keyword to use for %substitution + // Keyword to use for %substitution. 'keyword' => 'user', 'description' => t('Creates a user context from a user name.'), 'context' => 'ctools_argument_user_name_context', @@ -42,6 +41,3 @@ function ctools_argument_user_name_context($arg = NULL, $conf = NULL, $empty = F } return ctools_context_create('user', $account); } - - - diff --git a/sites/all/modules/contrib/dev/ctools/plugins/arguments/vid.inc b/sites/all/modules/contrib/dev/ctools/plugins/arguments/vid.inc index 064b22d..db68acd 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/arguments/vid.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/arguments/vid.inc @@ -2,8 +2,7 @@ /** * @file - * - * Plugin to provide an argument handler for a vocabulary id + * Plugin to provide an argument handler for a vocabulary id. */ /** @@ -12,7 +11,7 @@ */ $plugin = array( 'title' => t("Vocabulary: ID"), - // keyword to use for %substitution + // Keyword to use for %substitution. 'keyword' => 'vocabulary', 'description' => t('Creates a vocabulary context from a vocabulary ID argument.'), 'context' => 'ctools_vid_context', @@ -43,4 +42,3 @@ function ctools_vid_context($arg = NULL, $conf = NULL, $empty = FALSE) { return ctools_context_create('vocabulary', $vocabulary); } - diff --git a/sites/all/modules/contrib/dev/ctools/plugins/cache/export_ui.inc b/sites/all/modules/contrib/dev/ctools/plugins/cache/export_ui.inc new file mode 100644 index 0000000..f21af10 --- /dev/null +++ b/sites/all/modules/contrib/dev/ctools/plugins/cache/export_ui.inc @@ -0,0 +1,39 @@ + t('Export UI wizard cache'), + 'cache get' => 'ctools_cache_export_ui_cache_get', + 'cache set' => 'ctools_cache_export_ui_cache_set', + // Some operations use a 'finalize' but that really just means set + // for us, since we're not using temporary storage for subsystems. + 'cache finalize' => 'ctools_cache_export_ui_cache_set', +); + +function ctools_cache_export_ui_cache_get($plugin_name, $key) { + ctools_include('export-ui'); + $plugin = ctools_get_export_ui($plugin_name); + $handler = ctools_export_ui_get_handler($plugin); + if ($handler) { + $item = $handler->edit_cache_get($key); + if (!$item) { + $item = ctools_export_crud_load($handler->plugin['schema'], $key); + } + return $item; + } +} + +function ctools_cache_export_ui_cache_set($plugin_name, $key, $item) { + ctools_include('export-ui'); + $plugin = ctools_get_export_ui($plugin_name); + $handler = ctools_export_ui_get_handler($plugin); + if ($handler) { + return $handler->edit_cache_set_key($item, $key); + } +} diff --git a/sites/all/modules/contrib/dev/ctools/plugins/cache/simple.inc b/sites/all/modules/contrib/dev/ctools/plugins/cache/simple.inc new file mode 100644 index 0000000..c44d798 --- /dev/null +++ b/sites/all/modules/contrib/dev/ctools/plugins/cache/simple.inc @@ -0,0 +1,51 @@ + t('Simple'), + 'cache get' => 'ctools_cache_simple_cache_get', + 'cache set' => 'ctools_cache_simple_cache_set', + 'cache clear' => 'ctools_cache_simple_cache_clear', +); + +function ctools_cache_simple_cache_get($data, $key) { + ctools_include('object-cache'); + + // Ensure that if there is somehow no data, we at least don't stomp on other + // people's caches. + if (empty($data)) { + $data = 'simple_cache_plugin'; + } + + return ctools_object_cache_get($data, $key); +} + +function ctools_cache_simple_cache_set($data, $key, $object) { + ctools_include('object-cache'); + + // Ensure that if there is somehow no data, we at least don't stomp on other + // people's caches. + if (empty($data)) { + $data = 'simple_cache_plugin'; + } + + return ctools_object_cache_set($data, $key, $object); +} + +function ctools_cache_simple_cache_clear($data, $key) { + ctools_include('object-cache'); + + // Ensure that if there is somehow no data, we at least don't stomp on other + // people's caches. + if (empty($data)) { + $data = 'simple_cache_plugin'; + } + + return ctools_object_cache_clear($data, $key); +} diff --git a/sites/all/modules/contrib/dev/ctools/plugins/content_types/block/block.inc b/sites/all/modules/contrib/dev/ctools/plugins/content_types/block/block.inc index 9d55e8a..ff75c75 100644 --- a/sites/all/modules/contrib/dev/ctools/plugins/content_types/block/block.inc +++ b/sites/all/modules/contrib/dev/ctools/plugins/content_types/block/block.inc @@ -40,13 +40,18 @@ function ctools_block_content_type_content_type($subtype_id) { * of the form "$module . '_ctools_block_info'". */ function ctools_block_content_type_content_types() { + $types = &drupal_static(__FUNCTION__); + if (isset($types)) { + return $types; + } + $types = array(); foreach (module_implements('block_info') as $module) { $module_blocks = module_invoke($module, 'block_info'); if ($module_blocks) { foreach ($module_blocks as $delta => $block) { $info = _ctools_block_content_type_content_type($module, $delta, $block); - // this check means modules can remove their blocks; particularly useful + // This check means modules can remove their blocks; particularly useful // if they offer the block some other way (like we do for views) if ($info) { $types["$module-$delta"] = $info; @@ -143,27 +148,32 @@ function ctools_block_content_type_render($subtype, $conf) { $block = module_invoke($module, 'block_view', $delta); if (!empty($info)) { + // Valid PHP function names cannot contain hyphens. + $block_delta = str_replace('-', '_', $delta); + // Allow modules to modify the block before it is viewed, via either // hook_block_view_alter() or hook_block_view_MODULE_DELTA_alter(). - drupal_alter(array('block_view', "block_view_{$module}_{$delta}"), $block, $info); + drupal_alter(array('block_view', "block_view_{$module}_{$block_delta}"), $block, $info); } - $block = (object) $block; if (empty($block)) { return; } + $block = (object) $block; $block->module = $module; $block->delta = $delta; - if ($module == 'block' && !empty($info) && isset($info->title)) { - $block->title = $info->title; - } - else if (isset($block->subject)) { - $block->title = $block->subject; - } - else { - $block->title = NULL; + if (!isset($block->title)) { + if ($module == 'block' && !empty($info) && isset($info->title)) { + $block->title = $info->title; + } + elseif (isset($block->subject)) { + $block->title = $block->subject; + } + else { + $block->title = NULL; + } } if (module_exists('block') && user_access('administer blocks')) { @@ -198,70 +208,6 @@ function ctools_block_content_type_edit_form_submit($form, &$form_state) { } } -/** - * Returns an edit form for a block. - */ -//function ctools_block_content_type_edit_form($id, $parents, $conf) { -// if (user_access('administer advanced pane settings')) { -// $form['block_visibility'] = array( -// '#type' => 'checkbox', -// '#title' => t('Use block visibility settings (see block config)'), -// '#default_value' => !empty($conf['block_visibility']), -// '#description' => t('If checked, the block visibility settings for this block will apply to this block.'), -// ); -// // Module-specific block configurations. -// if ($settings = module_invoke($module, 'block', 'configure', $delta)) { -// // Specifically modify a couple of core block forms. -// if ($module == 'block') { -// unset($settings['submit']); -// $settings['info']['#type'] = 'value'; -// $settings['info']['#value'] = $settings['info']['#default_value']; -// } -// ctools_admin_fix_block_tree($settings); -// $form['block_settings'] = array( -// '#type' => 'fieldset', -// '#title' => t('Block settings'), -// '#description' => t('Settings in this section are global and are for all blocks of this type, anywhere in the system.'), -// '#tree' => FALSE, -// ); -// -// -// $form['block_settings'] += $settings; -// } -// } -// -// return $form; -//} - -//function ctools_admin_submit_block(&$form_values) { -// if (!empty($form_values['block_settings'])) { -// module_invoke($form_values['module'], 'block', 'save', $form_values['delta'], $form_values['block_settings']); -// } -//} -// -///** -// * Because form api cannot collapse just part of a tree, and the block settings -// * assume no tree, we have to collapse the tree ourselves. -// */ -//function ctools_admin_fix_block_tree(&$form, $key = NULL) { -// if ($key) { -// if (!empty($form['#parents'])) { -// $form['#parents'] = array_merge(array('configuration', 'block_settings'), $form['#parents']); -// } -// else if (empty($form['#tree'])) { -// $form['#parents'] = array('configuration', 'block_settings', $key); -// } -// } -// -// if (isset($form['#type']) && $form['#type'] == 'textarea' && !empty($form['#rows']) && $form['#rows'] > 10) { -// $form['#rows'] = 10; -// } -// -// foreach (element_children($form) as $key) { -// ctools_admin_fix_block_tree($form[$key], $key); -// } -//} - /** * Returns the administrative title for a type. */ @@ -286,13 +232,18 @@ function ctools_block_content_type_admin_info($subtype, $conf) { list($module, $delta) = _ctools_block_get_module_delta($subtype, $conf); $block = (object) module_invoke($module, 'block_view', $delta); - // Sanitize the block because \n"; + print '\n"; } +/** + * Returns whether query logging is enabled. + * + * @return boolean + * TRUE if the query log is enabled, FALSE otherwise. + */ function devel_query_enabled() { return method_exists('Database', 'getLog') && variable_get('devel_query_display', FALSE); } +/** + * Returns the query summary. + * + * @return array + * An index array containing: + * 0: The an associative array where the keys are the queries and the values + * are number of times that query was executes. + * 1: The summary. + * 2: An associative array of substitution variables to be applied in the + * previous element. + */ function devel_query_summary($queries) { if (variable_get('devel_query_display', FALSE) && is_array($queries)) { $sum = 0; @@ -1182,26 +1325,43 @@ function devel_query_summary($queries) { $sum += $query['time']; } $counts = array_count_values($text); - return array($counts, t_safe('Executed @queries queries in @time ms.', array('@queries' => count($queries), '@time' => round($sum * 1000, 2)))); + return array( + $counts, + t_safe('Executed @queries queries in @time ms.', + array('@queries' => count($queries), '@time' => round($sum * 1000, 2))), + ); } } +/** + * Gets the t() function if available, uses strtr() as a fallback. + */ function t_safe($string, $args) { - // get_t caused problems here with theme registry after changing on admin/build/modules. the theme_get_registry call is needed. + // get_t() caused problems here with the theme registry after changing on + // admin/build/modules. The theme_get_registry() call is needed! if (function_exists('t') && function_exists('theme_get_registry')) { theme_get_registry(); return t($string, $args); } else { - strtr($string, $args); + return strtr($string, $args); } } +/** + * Returns the core version. + * + * @param string + * The version string to parse. + * + * @return string + * MAJOR.MINOR for versions before 5, MAJOR for subsequent versions. + */ function devel_get_core_version($version) { $version_parts = explode('.', $version); // Map from 4.7.10 -> 4.7 if ($version_parts[0] < 5) { - return $version_parts[0] .'.'. $version_parts[1]; + return $version_parts[0] . '.' . $version_parts[1]; } // Map from 5.5 -> 5 or 6.0-beta2 -> 6 else { @@ -1209,31 +1369,37 @@ function devel_get_core_version($version) { } } -// See http://drupal.org/node/126098 +/** + * Returns whether the optimizer is compatible. + * + * @return boolean + * TRUE if compatible, FALSE otherwise. + */ function devel_is_compatible_optimizer() { - ob_start(); - phpinfo(); - $info = ob_get_contents(); - ob_end_clean(); + // See http://drupal.org/node/126098. + ob_start(); + phpinfo(); + $info = ob_get_contents(); + ob_end_clean(); - // Match the Zend Optimizer version in the phpinfo information - $found = preg_match('/Zend Optimizer v([0-9])\.([0-9])\.([0-9])/', $info, $matches); + // Match the Zend Optimizer version in the phpinfo information. + $found = preg_match('/Zend Optimizer v([0-9])\.([0-9])\.([0-9])/', $info, $matches); - if ($matches) { - $major = $matches[1]; - $minor = $matches[2]; - $build = $matches[3]; + if ($matches) { + $major = $matches[1]; + $minor = $matches[2]; + $build = $matches[3]; - if ($major >= 3) { - if ($minor >= 3) { - return TRUE; - } - elseif ($minor == 2 && $build >= 8) { - return TRUE; - } - else { - return FALSE; - } + if ($major >= 3) { + if ($minor >= 3) { + return TRUE; + } + elseif ($minor == 2 && $build >= 8) { + return TRUE; + } + else { + return FALSE; + } } else { return FALSE; @@ -1269,7 +1435,11 @@ function devel_execute_form() { '#default_value' => (isset($_SESSION['devel_execute_code']) ? $_SESSION['devel_execute_code'] : ''), '#rows' => 20, ); - $form['execute']['op'] = array('#type' => 'submit', '#value' => t('Execute')); + $form['execute']['actions'] = array('#type' => 'actions'); + $form['execute']['actions']['op'] = array( + '#type' => 'submit', + '#value' => t('Execute'), + ); $form['#redirect'] = FALSE; if (isset($_SESSION['devel_execute_code'])) { unset($_SESSION['devel_execute_code']); @@ -1278,7 +1448,7 @@ function devel_execute_form() { } /** - * Process PHP execute form submissions. + * Processes PHP execute form submissions. */ function devel_execute_form_submit($form, &$form_state) { ob_start(); @@ -1288,10 +1458,13 @@ function devel_execute_form_submit($form, &$form_state) { } /** - * Switch from original user to another user and back. - * We don't call session_save_session() because we really want to change users. Usually unsafe! + * Switches to a different user. * - * @param $name The username to switch to, or NULL to log out. + * We don't call session_save_session() because we really want to change users. + * Usually unsafe! + * + * @param string $name + * The username to switch to, or NULL to log out. */ function devel_switch_user($name = NULL) { global $user; @@ -1317,18 +1490,23 @@ function devel_switch_user($name = NULL) { } /** - * Print an object or array using either Krumo (if installed) or devel_print_object() + * Prints an object using either Krumo (if installed) or devel_print_object(). * - * @param $object - * array or object to print - * @param $prefix - * prefixing for output items + * @param array|object $object + * An array or object to print. + * @param string $prefix + * Prefix for output items. + * + * @return + * The results from krumo_ob() or devel_print_object(). */ function kdevel_print_object($object, $prefix = NULL) { return has_krumo() ? krumo_ob($object) : devel_print_object($object, $prefix); } -// Save krumo htlm using output buffering. +/** + * Saves krumo html using output buffering. + */ function krumo_ob($object) { ob_start(); krumo($object); @@ -1338,20 +1516,26 @@ function krumo_ob($object) { } /** - * Display an object or array + * Displays an object or array. * - * @param $object - * the object or array - * @param $prefix - * prefix for the output items (example "$node->", "$user->", "$") - * @param $header - * set to FALSE to suppress the output of the h3 + * @param array|object $object + * The object or array to display. + * @param string $prefix + * Prefix for the output items (example "$node->", "$user->", "$"). + * @param boolean $header + * Set to FALSE to suppress the output of the h3 tag. + * + * @return string */ function devel_print_object($object, $prefix = NULL, $header = TRUE) { - drupal_add_css(drupal_get_path('module', 'devel') .'/devel.css'); + drupal_add_css(drupal_get_path('module', 'devel') . '/devel.css'); $output = '
    '; if ($header) { - $output .= '

    '. t('Display of !type !obj', array('!type' => str_replace(array('$', '->'), '', $prefix), '!obj' => gettype($object))) .'

    '; + $output .= '

    ' . t('Display of !type !obj', array( + '!type' => str_replace(array('$', '->'), '', $prefix), + '!obj' => gettype($object), + ) + ) . '

    '; } $output .= _devel_print_object($object, $prefix); $output .= '
    '; @@ -1359,22 +1543,25 @@ function devel_print_object($object, $prefix = NULL, $header = TRUE) { } /** - * Recursive (and therefore magical) function goes through an array or object and - * returns a nicely formatted listing of its contents. + * Returns formatted listing for an array or object. * - * @param $obj - * array or object to recurse through - * @param $prefix - * prefix for the output items (example "$node->", "$user->", "$") - * @param $parents - * used by recursion - * @param $object - * used by recursion - * @return - * fomatted html + * Recursive (and therefore magical) function goes through an array or object + * and returns a nicely formatted listing of its contents. + * + * @param array|object $obj + * Array or object to recurse through. + * @param string $prefix + * Prefix for the output items (example "$node->", "$user->", "$"). + * @param string $parents + * Used by recursion. + * @param boolean $object + * Used by recursion. + * + * @return string + * Formatted html. * * @todo - * currently there are problems sending an array with a varname + * Currently there are problems sending an array with a varname. */ function _devel_print_object($obj, $prefix = NULL, $parents = NULL, $object = FALSE) { static $root_type, $out_format; @@ -1392,24 +1579,24 @@ function _devel_print_object($obj, $prefix = NULL, $parents = NULL, $object = FA } if (is_object($obj)) { - $obj = (array)$obj; + $obj = (array) $obj; } if (is_array($obj)) { $output = "
    \n"; foreach ($obj as $field => $value) { - if ($field == 'devel_flag_reference') { + if ($field === 'devel_flag_reference') { continue; } if (!is_null($parents)) { if ($object) { - $field = $parents .'->'. $field; + $field = $parents . '->' . $field; } else { if (is_int($field)) { - $field = $parents .'['. $field .']'; + $field = $parents . '[' . $field . ']'; } else { - $field = $parents .'[\''. $field .'\']'; + $field = $parents . '[\'' . $field . '\']'; } } } @@ -1420,9 +1607,9 @@ function _devel_print_object($obj, $prefix = NULL, $parents = NULL, $object = FA $summary = NULL; if ($show_summary) { switch ($type) { - case 'string' : - case 'float' : - case 'integer' : + case 'string': + case 'float': + case 'integer': if (strlen($value) == 0) { $summary = t("{empty}"); } @@ -1433,20 +1620,20 @@ function _devel_print_object($obj, $prefix = NULL, $parents = NULL, $object = FA $summary = format_plural(drupal_strlen($value), '1 character', '@count characters'); } break; - case 'array' : - case 'object' : - $summary = format_plural(count((array)$value), '1 element', '@count elements'); + case 'array': + case 'object': + $summary = format_plural(count((array) $value), '1 element', '@count elements'); break; - case 'boolean' : + case 'boolean': $summary = $value ? t('TRUE') : t('FALSE'); break; } } if (!is_null($summary)) { - $typesum = '('. $type .', '. $summary .')'; + $typesum = '(' . $type . ', ' . $summary . ')'; } else { - $typesum = '('. $type .')'; + $typesum = '(' . $type . ')'; } $output .= ''; @@ -1463,11 +1650,11 @@ function _devel_print_object($obj, $prefix = NULL, $parents = NULL, $object = FA } elseif (is_object($value)) { $value->devel_flag_reference = FALSE; - $output .= _devel_print_object((array)$value, $prefix, $field, TRUE); + $output .= _devel_print_object((array) $value, $prefix, $field, TRUE); } else { $value = is_bool($value) ? ($value ? 'TRUE' : 'FALSE') : $value; - $output .= htmlspecialchars(print_r($value, TRUE)) ."\n"; + $output .= htmlspecialchars(print_r($value, TRUE)) . "\n"; } $output .= "\n"; } @@ -1477,14 +1664,20 @@ function _devel_print_object($obj, $prefix = NULL, $parents = NULL, $object = FA } /** - * Adds a table at the bottom of the page cataloguing data on all the database queries that were made to - * generate the page. + * Shows all the queries for the page. + * + * Adds a table at the bottom of the page cataloguing data on all the database + * queries that were made to generate the page. + * + * @return string + * Queries themed using devel_querylog. */ function devel_query_table($queries, $counts) { $version = devel_get_core_version(VERSION); - $header = array ('ms', '#', 'where', 'ops', 'query', 'target'); + $header = array('ms', '#', 'where', 'ops', 'query', 'target'); $i = 0; $api = variable_get('devel_api_url', 'api.drupal.org'); + $conn = Database::getconnection(); foreach ($queries as $query) { $function = !empty($query['caller']['class']) ? $query['caller']['class'] . '::' : ''; $function .= $query['caller']['function']; @@ -1492,23 +1685,49 @@ function devel_query_table($queries, $counts) { $diff = round($query['time'] * 1000, 2); if ($diff > variable_get('devel_execution', 5)) { - $cell[$i][] = array ('data' => $diff, 'class' => 'marker'); + $cell[$i][] = array('data' => $diff, 'class' => 'marker'); } else { $cell[$i][] = $diff; } $cell[$i][] = $count; $cell[$i][] = l($function, "http://$api/api/function/$function/$version"); - $ops[] = l('P', '', array('attributes' => array('title' => 'Show placeholders', 'class' => 'dev-placeholders', 'qid' => $i))); - $ops[] = l('A', '', array('attributes' => array('title' => 'Show arguments', 'class' => 'dev-arguments', 'qid' => $i))); + $ops[] = l(t('P'), '', array( + 'attributes' => array( + 'title' => 'Show placeholders', + 'class' => array('dev-placeholders'), + 'qid' => $i, + ))); + $ops[] = l(t('A'), '', array( + 'attributes' => array( + 'title' => 'Show arguments', + 'class' => array('dev-arguments'), + 'qid' => $i, + ))); // EXPLAIN only valid for select queries. if (strpos($query['query'], 'UPDATE') === FALSE && strpos($query['query'], 'INSERT') === FALSE && strpos($query['query'], 'DELETE') === FALSE) { - $ops[] = l('E', '', array('attributes' => array('title' => 'Show EXPLAIN', 'class' => 'dev-explain', 'qid' => $i))); + $ops[] = l(t('E'), '', array( + 'attributes' => array( + 'title' => 'Show EXPLAIN', + 'class' => array('dev-explain'), + 'qid' => $i, + ))); } $cell[$i][] = implode(' ', $ops); // 3 divs for each variation of the query. Last 2 are hidden by default. - $placeholders = '
    ' . check_plain($query['query']) . "
    \n"; - $args = '' . "\n"; + if (variable_get('devel_show_query_args_first', FALSE)) { + $placeholders = '\n"; + $quoted = array(); + foreach ($query['args'] as $key => $val) { + $quoted[$key] = $conn->quote($val); + } + $output = strtr($query['query'], $quoted); + $args = '
    ' . $output . '
    ' . "\n"; + } + else { + $placeholders = '
    ' . check_plain($query['query']) . "
    \n"; + $args = '' . "\n"; + } $explain = '' . "\n"; $cell[$i][] = array( 'id' => "devel-query-$i", @@ -1524,6 +1743,18 @@ function devel_query_table($queries, $counts) { return theme('devel_querylog', array('header' => $header, 'rows' => $cell)); } +/** + * Returns HTML for a Devel querylog row. + * + * @param array $variables + * An associative array containing: + * - row: An array of cells in which each cell is either a string or an + * associative array containing: + * - data: The data to render. + * - Any attributes to be applied to the cell. + * + * @ingroup themeable + */ function theme_devel_querylog_row($variables) { $row = $variables['row']; $i = 0; @@ -1554,6 +1785,16 @@ function theme_devel_querylog_row($variables) { return $output; } +/** + * Returns HTML for the Devel querylog. + * + * @param array $variables + * An associative array containing: + * - header: An array suitable for rendering with devel_querylog_row. + * - rows: An array of rows suitable for rendering with devel_querylog_row. + * + * @ingroup themeable + */ function theme_devel_querylog($variables) { $header = $variables['header']; $rows = $variables['rows']; @@ -1576,6 +1817,9 @@ function theme_devel_querylog($variables) { return $output; } +/** + * Devel's table sort. + */ function _devel_table_sort($a, $b) { $a = is_array($a[0]) ? $a[0]['data'] : $a[0]; $b = is_array($b[0]) ? $b[0]['data'] : $b[0]; @@ -1590,28 +1834,36 @@ function _devel_table_sort($a, $b) { /** * Displays page execution time at the bottom of the page. + * + * @return string + * A message indicating how long it took to execute the page. */ function devel_timer() { $time = timer_read('page'); return t_safe(' Page execution time was @time ms.', array('@time' => $time)); } -// An alias for drupal_debug(). -function dd($data, $label = NULL) { - return drupal_debug($data, $label); +if (!function_exists('dd')) { + /** + * An alias for drupal_debug(). + * + * @see drupal_debug() + */ + function dd($data, $label = NULL) { + return drupal_debug($data, $label); + } } /** * Logs a variable to a drupal_debug.txt in the site's temp directory. * - * @param $data + * @param mixed $data * The variable to log to the drupal_debug.txt log file. - * @param $label + * @param string $label * (optional) If set, a label to output before $data in the log file. * - * @return - * No return value if successful, FALSE if the log file could not be written - * to. + * @return void|false + * Empty if successful, FALSE if the log file could not be written. * * @see dd() * @see http://drupal.org/node/314112 @@ -1620,15 +1872,15 @@ function drupal_debug($data, $label = NULL) { $out = ($label ? $label . ': ' : '') . print_r($data, TRUE) . "\n"; // The temp directory does vary across multiple simpletest instances. - $file = 'temporary://drupal_debug.txt'; + $file = file_directory_temp() . '/drupal_debug.txt'; if (file_put_contents($file, $out, FILE_APPEND) === FALSE) { - drupal_set_message(t('The file could not be written.'), 'error'); + drupal_set_message(t('Devel was unable to write to %file.', array('%file' => $file)), 'error'); return FALSE; } } /** - * Prints the arguments passed into the current function + * Prints the arguments passed into the current function. */ function dargs($always = TRUE) { static $printed; @@ -1640,27 +1892,30 @@ function dargs($always = TRUE) { } /** - * Prints a SQL string from a DBTNG SelectQuery object. + * Prints a SQL string from a DBTNG Select object. * * Includes quoted arguments. * - * @param $query + * @param object $query * An object that implements the SelectQueryInterface interface. - * @param $return + * @param string $return * Whether to return the string. Default is FALSE, meaning to print it * and return $query instead. - * @param $name + * @param string $name * Optional name for identifying the output. - * @return + * + * @return object|string * The $query object, or the query string if $return was TRUE. */ function dpq($query, $return = FALSE, $name = NULL) { if (user_access('access devel information')) { - $query->preExecute(); + if (method_exists($query, 'preExecute')) { + $query->preExecute(); + } $sql = (string) $query; $quoted = array(); $connection = Database::getConnection(); - foreach ((array)$query->arguments() as $key => $val) { + foreach ((array) $query->arguments() as $key => $val) { $quoted[$key] = $connection->quote($val); } $sql = strtr($sql, $quoted); @@ -1673,18 +1928,21 @@ function dpq($query, $return = FALSE, $name = NULL) { } /** - * Print a variable to the 'message' area of the page. + * Prints a variable to the 'message' area of the page. * * Uses drupal_set_message(). * * @param $input * An arbitrary value to output. - * @param $name + * @param string $name * Optional name for identifying the output. - * @param $type + * @param string $type * Optional message type for drupal_set_message(), defaults to 'status'. - * @return + * + * @return input * The unaltered input value. + * + * @see drupal_set_message() */ function dpm($input, $name = NULL, $type = 'status') { if (user_access('access devel information')) { @@ -1695,16 +1953,20 @@ function dpm($input, $name = NULL, $type = 'status') { } /** - * drupal_var_export() a variable to the 'message' area of the page. + * Displays a drupal_var_export() variable to the 'message' area of the page. * * Uses drupal_set_message(). * * @param $input * An arbitrary value to output. - * @param $name + * @param string $name * Optional name for identifying the output. - * @return + * + * @return input * The unaltered input value. + * + * @see drupal_set_message() + * @see drupal_var_export() */ function dvm($input, $name = NULL) { if (user_access('access devel information')) { @@ -1714,37 +1976,73 @@ function dvm($input, $name = NULL) { return $input; } -// legacy function that was poorly named. use dpm() instead, since the 'p' maps to 'print_r' +/** + * Legacy function that was poorly named. + * + * @deprecated Use dpm() instead, since the 'p' maps to 'print_r'. + * + * @see dpm() + */ function dsm($input, $name = NULL) { return dpm($input, $name); } /** - * An alias for dprint_r(). Saves carpal tunnel syndrome. + * An alias for dprint_r(). + * + * Saves carpal tunnel syndrome. + * + * @see dprint_r() */ function dpr($input, $return = FALSE, $name = NULL) { return dprint_r($input, $return, $name); } /** - * An alias for kprint_r(). Saves carpal tunnel syndrome. + * An alias for kprint_r(). + * + * Saves carpal tunnel syndrome. + * + * @see kprint_r() */ function kpr($input, $return = FALSE, $name = NULL) { return kprint_r($input, $return, $name); } /** - * Like dpr, but uses drupal_var_export() instead + * Like dpr(), but uses drupal_var_export() instead. + * + * @see dprint_r() + * @see drupal_var_export() */ function dvr($input, $return = FALSE, $name = NULL) { return dprint_r($input, $return, $name, 'drupal_var_export', FALSE); } +/** + * Returns a message using Krumo. + * + * Uses dprint_r() as a fallback. + * + * @param mixed $input + * The thing to print. + * @param boolean $return + * (optional) Indicates if the output should be returned. The default is + * FALSE. + * @param string $name + * (optional) The label to apply. + * @param string $function + * (optional) The function to use for output in the case where dprint_r() is + * used. The defualt is print_r(). + * + * @return + * The output if $return is TRUE. + */ function kprint_r($input, $return = FALSE, $name = NULL, $function = 'print_r') { - // We do not want to krumo() strings and integers and such + // We do not want to krumo() strings and integers and such. if (merits_krumo($input)) { if (user_access('access devel information')) { - return $return ? (isset($name) ? $name .' => ' : '') . krumo_ob($input) : krumo($input); + return $return ? (isset($name) ? $name . ' => ' : '') . krumo_ob($input) : krumo($input); } } else { @@ -1754,10 +2052,27 @@ function kprint_r($input, $return = FALSE, $name = NULL, $function = 'print_r') /** * Pretty-print a variable to the browser (no krumo). - * Displays only for users with proper permissions. If - * you want a string returned instead of a print, use the 2nd param. + * + * Displays only for users with proper permissions. If you want a string + * returned instead of a print, use the 2nd param. + * + * @param mixed $input + * The input that should be printed or returned. + * @param boolean $return + * (optional) Indicates of the result should be returned instead of printed. + * The default is to print it. + * @param string $name + * (optional) The label to apply. + * @param string $function + * (optional) The function to use for output. The defualt is print_r(). + * @param boolean $check + * (optional) Indicates if the output should be run through check_plain(). + * The default is TRUE. + * + * @return + * The formatted output if $return is TRUE. */ -function dprint_r($input, $return = FALSE, $name = NULL, $function = 'print_r', $check= TRUE) { +function dprint_r($input, $return = FALSE, $name = NULL, $function = 'print_r', $check = TRUE) { if (user_access('access devel information')) { if ($name) { $name .= ' => '; @@ -1775,12 +2090,12 @@ function dprint_r($input, $return = FALSE, $name = NULL, $function = 'print_r', if ($check) { $output = check_plain($output); } - if (count($input, COUNT_RECURSIVE) > DEVEL_MIN_TEXTAREA) { - // don't use fapi here because sometimes fapi will not be loaded - $printed_value = "'; + if (is_array($input) && count($input, COUNT_RECURSIVE) > DEVEL_MIN_TEXTAREA) { + // Don't use fapi here because sometimes fapi will not be loaded. + $printed_value = "'; } else { - $printed_value = '
    '. $name . $output .'
    '; + $printed_value = '
    ' . $name . $output . '
    '; } if ($return) { @@ -1809,18 +2124,35 @@ function devel_render() { } /** - * Print the function call stack. + * Prints the function call stack. + * + * @param $return + * Pass TRUE to return the formatted backtrace rather than displaying it in + * the browser via kprint_r(). + * @param $pop + * How many items to pop from the top of the stack; useful when calling from + * an error handler. + * @param $options + * Options to pass on to PHP's debug_backtrace(), depending on your PHP + * version. + * + * @return string|NULL + * The formatted backtrace, if requested, or NULL. + * + * @see http://php.net/manual/en/function.debug-backtrace.php */ -function ddebug_backtrace($return = FALSE, $pop = 0) { +function ddebug_backtrace($return = FALSE, $pop = 0, $options = TRUE) { if (user_access('access devel information')) { - $backtrace = debug_backtrace(); + $backtrace = debug_backtrace($options); while ($pop-- > 0) { array_shift($backtrace); } $counter = count($backtrace); - $path = $backtrace[$counter - 1]['file']; - $path = substr($path, 0, strlen($path) - 10); - $paths[$path] = strlen($path) + 1; + if (!empty($backtrace[$counter - 1]['file'])) { + $path = $backtrace[$counter - 1]['file']; + $path = substr($path, 0, strlen($path) - 10); + $paths[$path] = strlen($path) + 1; + } $paths[DRUPAL_ROOT] = strlen(DRUPAL_ROOT) + 1; $nbsp = "\xC2\xA0"; @@ -1847,6 +2179,7 @@ function ddebug_backtrace($return = FALSE, $pop = 0) { else { $function = $backtrace[1]['function'] . '()'; } + $backtrace[1] += array('args' => array()); $call['args'] = $backtrace[1]['args']; } else { @@ -1864,31 +2197,32 @@ function ddebug_backtrace($return = FALSE, $pop = 0) { } } -// Delete all files in a dir. +/** + * Deletes all files in a dir. + */ function devel_empty_dir($dir) { - foreach (new DirectoryIterator($dir) as $fileInfo) { - if ($fileInfo->isFile()) { - unlink($fileInfo->getPathname()); + foreach (new DirectoryIterator($dir) as $file_info) { + if ($file_info->isFile()) { + unlink($file_info->getPathname()); } } } /* - * migration related functions + * Migration-related functions. */ /** - * Regenerate the data in node_comment_statistics table. Technique comes from - * http://www.artfulsoftware.com/infotree/queries.php?&bw=1280#101 + * Regenerates the data in node_comment_statistics table. * - * @return void - **/ + * Technique - http://www.artfulsoftware.com/infotree/queries.php?&bw=1280#101 + */ function devel_rebuild_node_comment_statistics() { - // Empty table + // Empty table. db_truncate('node_comment_statistics')->execute(); - // TODO: DBTNG. Ignore keyword is Mysql only? Is only used in the rare case when - // two comments on the same node share same timestamp. + // TODO: DBTNG. Ignore keyword is Mysql only? Is only used in the rare case + // when two comments on the same node share same timestamp. $sql = " INSERT IGNORE INTO {node_comment_statistics} (nid, cid, last_comment_timestamp, last_comment_name, last_comment_uid, comment_count) ( SELECT c.nid, c.cid, c.created, c.name, c.uid, c2.comment_count FROM {comment} c @@ -1908,7 +2242,7 @@ function devel_rebuild_node_comment_statistics() { $query->addExpression('NULL', 'last_comment_name'); $query->isNull('ncs.comment_count'); - db_insert('node_comment_statistics') + db_insert('node_comment_statistics', array('return' => Database::RETURN_NULL)) ->from($query) ->execute(); } @@ -1916,8 +2250,8 @@ function devel_rebuild_node_comment_statistics() { /** * Implements hook_form_alter(). * - * Adds mouse-over hints on the Permissions page to display - * language-independent machine names and module base names. + * Adds mouse-over hints on the Permissions page to display language-independent + * machine names and module base names. */ function devel_form_user_admin_permissions_alter(&$form, &$form_state) { if (user_access('access devel information') && variable_get('devel_raw_names', FALSE)) { @@ -1935,8 +2269,7 @@ function devel_form_user_admin_permissions_alter(&$form, &$form_state) { /** * Implements hook_form_alter(). * - * Adds mouse-over hints on the Modules page to display - * module base names. + * Adds mouse-over hints on the Modules page to display module base names. */ function devel_form_system_modules_alter(&$form, &$form_state) { if (user_access('access devel information') && variable_get('devel_raw_names', FALSE) && isset($form['modules']) && is_array($form['modules'])) { @@ -1954,3 +2287,25 @@ function devel_form_system_modules_alter(&$form, &$form_state) { } } } + +/** + * Implements hook_query_TAG_alter() for the devel tag. + * + * Makes debugging EntityFieldQuery much easier. + * + * Example usage: + * @code + * $q = new EntityFieldQuery; + * $q->entityCondition('entity_type', 'node') + * ->addTag('debug') + * ->execute(); + * @endcode + * + * @param QueryAlterableInterface $query + */ +function devel_query_debug_alter(QueryAlterableInterface $query) { + if (!$query->hasTag('debug-semaphore')) { + $query->addTag('debug-semaphore'); + dpq($query); + } +} diff --git a/sites/all/modules/contrib/dev/devel/devel.pages.inc b/sites/all/modules/contrib/dev/devel/devel.pages.inc index 9870f52..c93eda5 100644 --- a/sites/all/modules/contrib/dev/devel/devel.pages.inc +++ b/sites/all/modules/contrib/dev/devel/devel.pages.inc @@ -1,14 +1,23 @@ query, (array)$query->args)->fetchAllAssoc('table'); - $i = 1; - foreach ($result as $row) { - $row = (array)$row; - if ($i == 1) { - $header = array_keys($row); + + $output = t('No explain log found.'); + + if (file_exists($path)) { + $queries = json_decode(file_get_contents($path)); + + if ($queries !== FALSE && isset($queries[$qid])) { + $header = $rows = array(); + + $query = $queries[$qid]; + $result = db_query('EXPLAIN ' . $query->query, (array)$query->args)->fetchAllAssoc('table'); + $i = 1; + foreach ($result as $row) { + $row = (array)$row; + if ($i == 1) { + $header = array_keys($row); + } + $rows[] = array_values($row); + $i++; + } + + $output = theme('table', array('header' => $header, 'rows' => $rows)); } - $rows[] = array_values($row); - $i++; } + // Print and return nothing thus avoiding page wrapper. - $output = theme('table', array('header' => $header, 'rows' => $rows)); print $output; $GLOBALS['devel_shutdown'] = FALSE; } -// A menu callback. Called by the AJAX link in query log. +/** + * Page callback: Called by the AJAX link in query log. + */ function devel_querylog_arguments($request_id, $qid) { if (!is_numeric($request_id)) { return MENU_ACCESS_DENIED; @@ -67,22 +96,30 @@ function devel_querylog_arguments($request_id, $qid) { $path = "temporary://devel_querylog/$request_id.txt"; $path = file_stream_wrapper_uri_normalize($path); - $queries = json_decode(file_get_contents($path)); - $query = $queries[$qid]; - $conn = Database::getConnection(); - $quoted = array(); - foreach ((array)$query->args as $key => $val) { - $quoted[$key] = $conn->quote($val); - } - $output = strtr($query->query, $quoted); - // print and return nothing thus avoiding page wrapper. + $output = t('No arguments log found.'); + + if (file_exists($path)) { + $queries = json_decode(file_get_contents($path)); + + if ($queries !== FALSE && isset($queries[$qid])) { + $query = $queries[$qid]; + $conn = Database::getConnection(); + $quoted = array(); + foreach ((array)$query->args as $key => $val) { + $quoted[$key] = $conn->quote($val); + } + $output = strtr($query->query, $quoted); + } + } + + // Print and return nothing thus avoiding page wrapper. print $output; $GLOBALS['devel_shutdown'] = FALSE; } /** - * Menu callback; clear the database, resetting the menu to factory defaults. + * Page callback: Clears the database, resetting the menu to factory defaults. */ function devel_menu_rebuild() { menu_rebuild(); @@ -91,7 +128,11 @@ function devel_menu_rebuild() { } /** - * Display a dropdown of installed modules with the option to reinstall them. + * Form constructor for reinstalling any module from a dropdown. + * + * @see devel_reinstall_submit() + * + * @ingroup forms */ function devel_reinstall($form, &$form_state) { $output = ''; @@ -103,7 +144,8 @@ function devel_reinstall($form, &$form_state) { '#options' => $options, '#description' => t('Uninstall and then install the selected modules. hook_uninstall() and hook_install() will be executed and the schema version number will be set to the most recent update number. You may have to manually clear out any existing tables first if the module doesn\'t implement hook_uninstall().'), ); - $form['submit'] = array( + $form['actions'] = array('#type' => 'actions'); + $form['actions']['submit'] = array( '#value' => t('Reinstall'), '#type' => 'submit', ); @@ -111,7 +153,7 @@ function devel_reinstall($form, &$form_state) { } /** - * Process reinstall menu form submissions. + * Form submission callback for devel_reinstall(). */ function devel_reinstall_submit($form, &$form_state) { // require_once './includes/install.inc'; @@ -122,7 +164,9 @@ function devel_reinstall_submit($form, &$form_state) { drupal_set_message(t('Uninstalled and installed: %names.', array('%names' => implode(', ', $modules)))); } -// Menu callback. +/** + * Page callback: Rebuilds the theme registry. + */ function devel_theme_registry() { drupal_theme_initialize(); $hooks = theme_get_registry(); @@ -130,14 +174,26 @@ function devel_theme_registry() { return kprint_r($hooks, TRUE); } -// Menu callback. $entity_type argument not currently used in the UI. +/** + * Page callback: Returns information from hook_entity_info(). + * + * @param string $entity_type + * (optional) Limit results to the specified entity type. The default is to + * return information about all entity types. The $entity_type argument is not + * currently used in the UI. + * + * @return + * The results from kprint_r(). + */ function devel_entity_info_page($entity_type = NULL) { $info = entity_get_info($entity_type); ksort($info); return kprint_r($info, TRUE); } -// Menu callback. +/** + * Page callback: Returns information about fields. + */ function devel_field_info_page() { $info = field_info_fields(); $output = kprint_r($info, TRUE, t('Fields')); @@ -145,20 +201,24 @@ function devel_field_info_page() { $output .= kprint_r($info, TRUE, t('Instances')); $info = field_info_bundles(); $output .= kprint_r($info, TRUE, t('Bundles')); + $info = field_info_field_types(); + $output .= kprint_r($info, TRUE, t('Field types')); + $info = field_info_formatter_types(); + $output .= kprint_r($info, TRUE, t('Formatter types')); + $info = field_info_storage_types(); + $output .= kprint_r($info, TRUE, t('Storage types')); + $info = field_info_widget_types(); + $output .= kprint_r($info, TRUE, t('Widget types')); return $output; } /** - * Menu callback; display all variables. + * Form constructor for displaying and editing variables. + * + * @see devel_variable_form_submit() + * + * @ingroup forms. */ -function devel_variable_page() { - // We return our own $page so as to avoid blocks. - $output = drupal_get_form('devel_variable_form'); - drupal_set_page_content($output); - $page = element_info('page'); - return $page; -} - function devel_variable_form() { $header = array( 'name' => array('data' => t('Name'), 'field' => 'name', 'sort' => 'asc'), @@ -166,7 +226,9 @@ function devel_variable_form() { 'length' => array('data' => t('Length'), 'field' => 'length'), 'edit' => array('data' => t('Operations')), ); - // TODO: we could get variables out of $conf but that would include hard coded ones too. ideally i would highlight overrridden/hard coded variables + // @todo: We could get variables out of $conf but that would include + // hard-coded ones too. Ideally I would highlight overridden/hard-coded + // variables. $query = db_select('variable', 'v')->extend('TableSort'); $query->fields('v', array('name', 'value')); switch (db_driver()) { @@ -205,15 +267,20 @@ function devel_variable_form() { '#options' => $options, '#empty' => t('No variables.'), ); - $form['submit'] = array( + $form['actions'] = array('#type' => 'actions'); + $form['actions']['submit'] = array( '#type' => 'submit', '#value' => t('Delete'), ); + $form['#after_build'][] = 'devel_variable_form_after_build'; // krumo($form); return $form; } +/** + * Form submission handler for devel_variable_form(). + */ function devel_variable_form_submit($form, &$form_state) { $deletes = array_filter($form_state['values']['variables']); array_walk($deletes, 'variable_del'); @@ -222,6 +289,25 @@ function devel_variable_form_submit($form, &$form_state) { } } +/** + * After build callback for devel_variable_form(). + * + * Wrap each variable name in a

    in text fields - // case 1: // html - // for ($i = 1; $i <= $nparas; $i++) { - // $output .= devel_create_para(mt_rand(10,60),1); - // } - // break; - // - // case 2: // brs only - // for ($i = 1; $i <= $nparas; $i++) { - // $output .= devel_create_para(mt_rand(10,60),2); - // } - // break; - - default: // plain text - for ($i = 1; $i <= $nparas; $i++) { - $output .= devel_create_para(mt_rand(10,60)) ."\n\n"; - } +function devel_create_content() { + $nparas = mt_rand(1, 12); + $output = ''; + for ($i = 1; $i <= $nparas; $i++) { + $output .= devel_create_para(mt_rand(10,60)) . "\n\n"; } return $output; @@ -661,8 +642,12 @@ function devel_generate_content_pre_node(&$results) { /** * Create one node. Used by both batch and non-batch code branches. * - * @param $num - * array of options obtained from devel_generate_content_form. + * @param $results + * array of options obtained from devel_generate_content_form. If call + * this function directly, $results should contain at the very least: + * node_types => an associative array of node type machine names + * users => an array of UIDs + * title_length => max number of words in titles, for example 4. */ function devel_generate_content_add_node(&$results) { $node = new stdClass(); @@ -674,9 +659,19 @@ function devel_generate_content_add_node(&$results) { $users = $results['users']; $node->uid = $users[array_rand($users)]; $type = node_type_get_type($node); - $node->title = $type->has_title ? devel_create_greeking(mt_rand(2, $results['title_length']), TRUE) : ''; $node->revision = mt_rand(0,1); $node->promote = mt_rand(0, 1); + + if (!$type || $type->has_title) { + // We should not use the random function if the value is not random + if ($results['title_length'] < 2) { + $node->title = devel_create_greeking(1, TRUE); + } + else { + $node->title = devel_create_greeking(mt_rand(1, $results['title_length']), TRUE); + } + } + // Avoid NOTICE. if (!isset($results['time_range'])) { $results['time_range'] = 0; @@ -686,14 +681,16 @@ function devel_generate_content_add_node(&$results) { $node->created = REQUEST_TIME - mt_rand(0, $results['time_range']); - // A flag to let hook_nodeapi() implementations know that this is a generated node. + // A flag to let hook_node_insert() implementations know that this is a + // generated node. $node->devel_generate = $results; // Populate all core fields on behalf of field.module module_load_include('inc', 'devel_generate', 'devel_generate.fields'); devel_generate_fields($node, 'node', $node->type); - // See devel_generate_nodeapi() for actions that happen before and after this save. + // See devel_generate_node_insert() for actions that happen before and after + // this save. node_save($node); } diff --git a/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.info b/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.info index 877b618..477d935 100644 --- a/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.info +++ b/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.info @@ -2,13 +2,12 @@ name = Devel generate description = Generate dummy users, nodes, and taxonomy terms. package = Development core = 7.x -dependencies[] = devel tags[] = developer configure = admin/config/development/generate +files[] = devel_generate.test -; Information added by drupal.org packaging script on 2012-06-05 -version = "7.x-1.3" +; Information added by Drupal.org packaging script on 2019-02-22 +version = "7.x-1.7" core = "7.x" project = "devel" -datestamp = "1338940281" - +datestamp = "1550852892" diff --git a/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.module b/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.module index 3776c22..d714dbe 100644 --- a/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.module +++ b/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.module @@ -71,6 +71,14 @@ function devel_generate_users_form() { '#options' => $options, ); + $form['pass'] = array( + '#type' => 'textfield', + '#title' => t('Password to be set'), + '#default_value' => NULL, + '#size' => 32, + '#description' => t('Leave this field empty if you do not need to set a password.'), + ); + $options = array(1 => t('Now')); foreach (array(3600, 86400, 604800, 2592000, 31536000) as $interval) { $options[$interval] = format_interval($interval, 1) . ' ' . t('ago'); @@ -83,7 +91,8 @@ function devel_generate_users_form() { '#default_value' => 604800, ); - $form['submit'] = array( + $form['actions'] = array('#type' => 'actions'); + $form['actions']['submit'] = array( '#type' => 'submit', '#value' => t('Generate'), ); @@ -93,10 +102,10 @@ function devel_generate_users_form() { /** * FormAPI submission to generate users. */ -function devel_generate_users_form_submit($form_id, &$form_state) { +function devel_generate_users_form_submit($form, &$form_state) { module_load_include('inc', 'devel_generate'); $values = $form_state['values']; - devel_create_users($values['num'], $values['kill_users'], $values['time_range'], $values['roles']); + devel_create_users($values['num'], $values['kill_users'], $values['time_range'], $values['roles'], $values['pass']); } /** @@ -117,14 +126,15 @@ function devel_generate_content_form() { } else { $types = node_type_get_types(); - $suffix = ''; foreach ($types as $type) { + $options[$type->type] = array( + 'type' => t($type->name), + ); if (module_exists('comment')) { $default = variable_get('comment_' . $type->type, COMMENT_NODE_OPEN); $map = array(t('Hidden'), t('Closed'), t('Open')); - $suffix = '. ' . t('Comments: ') . $map[$default]. ''; + $options[$type->type]['comments'] = ''. $map[$default]. ''; } - $options[$type->type] = t($type->name) . $suffix; } } // we cannot currently generate valid polls. @@ -135,11 +145,18 @@ function devel_generate_content_form() { return; } + $header = array( + 'type' => t('Content type'), + ); + if (module_exists('comment')) { + $header['comments'] = t('Comments'); + } + $form['node_types'] = array( - '#type' => 'checkboxes', - '#title' => t('Content types'), + '#type' => 'tableselect', + '#header' => $header, '#options' => $options, - '#default_value' => array_keys($options), + '#required' => TRUE, ); if (module_exists('checkall')) $form['node_types']['#checkall'] = TRUE; $form['kill_content'] = array( @@ -209,7 +226,8 @@ function devel_generate_content_form() { '#default_value' => array(LANGUAGE_NONE), ); - $form['submit'] = array( + $form['actions'] = array('#type' => 'actions'); + $form['actions']['submit'] = array( '#type' => 'submit', '#value' => t('Generate'), ); @@ -218,12 +236,21 @@ function devel_generate_content_form() { return $form; } +/** + * FormAPI validation before generate nodes. + */ +function devel_generate_content_form_validate($form, &$form_state) { + form_set_value($form['node_types'], array_filter($form_state['values']['node_types']) , $form_state); + if (!empty($form_state['values']['kill_content']) && empty($form_state['values']['node_types'])) { + form_set_error('', t('Please select at least one content type to delete the content.')); + } +} + /** * FormAPI submission to generate nodes. */ -function devel_generate_content_form_submit($form_id, &$form_state) { +function devel_generate_content_form_submit($form, &$form_state) { module_load_include('inc', 'devel_generate', 'devel_generate'); - $form_state['values']['node_types'] = array_filter($form_state['values']['node_types']); if ($form_state['values']['num_nodes'] <= 50 && $form_state['values']['max_comments'] <= 10) { module_load_include('inc', 'devel_generate'); devel_generate_content($form_state); @@ -267,7 +294,8 @@ function devel_generate_term_form() { '#title' => t('Delete existing terms in specified vocabularies before generating new terms.'), '#default_value' => FALSE, ); - $form['submit'] = array( + $form['actions'] = array('#type' => 'actions'); + $form['actions']['submit'] = array( '#type' => 'submit', '#value' => t('Generate'), ); @@ -295,7 +323,8 @@ function devel_generate_vocab_form() { '#title' => t('Delete existing vocabularies before generating new ones.'), '#default_value' => FALSE, ); - $form['submit'] = array( + $form['actions'] = array('#type' => 'actions'); + $form['actions']['submit'] = array( '#type' => 'submit', '#value' => t('Generate'), ); @@ -305,18 +334,36 @@ function devel_generate_vocab_form() { /** * FormAPI submission to generate taxonomy terms. */ -function devel_generate_term_form_submit($form_id, &$form_state) { +function devel_generate_term_form_submit($form, &$form_state) { + $values = $form_state['values']; module_load_include('inc', 'devel_generate'); - $vocabs = taxonomy_vocabulary_load_multiple($form_state['values']['vids']); - devel_generate_term_data($vocabs, $form_state['values']['num_terms'], $form_state['values']['title_length'], $form_state['values']['kill_taxonomy']); + if ($values['kill_taxonomy']) { + foreach ($values['vids'] as $vid) { + devel_generate_delete_vocabulary_terms($vid); + } + drupal_set_message(t('Deleted existing terms.')); + } + $vocabs = taxonomy_vocabulary_load_multiple($values['vids']); + $new_terms = devel_generate_terms($values['num_terms'], $vocabs, $values['title_length']); + if (!empty($new_terms)) { + drupal_set_message(t('Created the following new terms: !terms', array('!terms' => implode(', ', $new_terms)))); + } } /** * FormAPI submission to generate taxonomy vocabularies. */ -function devel_generate_vocab_form_submit($form_id, &$form_state) { +function devel_generate_vocab_form_submit($form, &$form_state) { + $values = $form_state['values']; module_load_include('inc', 'devel_generate'); - devel_generate_vocab_data($form_state['values']['num_vocabs'], $form_state['values']['title_length'], $form_state['values']['kill_taxonomy']); + if ($values['kill_taxonomy']) { + devel_generate_delete_vocabularies(); + drupal_set_message(t('Deleted existing vocabularies.')); + } + $new_vocs = devel_generate_vocabs($values['num_vocabs'], $values['title_length']); + if (!empty($new_vocs)) { + drupal_set_message(t('Created the following new vocabularies: !vocs', array('!vocs' => implode(', ', $new_vocs)))); + } } /** @@ -392,7 +439,7 @@ function devel_generate_menu_form() { '#size' => 10, '#states' => array( 'visible' => array( - ':input[name=existing_menus[__new-menu__]]' => array('checked' => TRUE), + ':input[name="existing_menus[__new-menu__]"]' => array('checked' => TRUE), ), ), ); @@ -407,6 +454,7 @@ function devel_generate_menu_form() { $form['title_length'] = array( '#type' => 'textfield', '#title' => t('Maximum number of characters in menu and menu link names'), + '#description' => t("The minimum length is 2."), '#default_value' => 12, '#size' => 10, '#required' => TRUE, @@ -443,7 +491,8 @@ function devel_generate_menu_form() { '#title' => t('Delete existing custom generated menus and menu links before generating new ones.'), '#default_value' => FALSE, ); - $form['submit'] = array( + $form['actions'] = array('#type' => 'actions'); + $form['actions']['submit'] = array( '#type' => 'submit', '#value' => t('Generate'), ); @@ -453,11 +502,26 @@ function devel_generate_menu_form() { /** * FormAPI submission to generate menus. */ -function devel_generate_menu_form_submit($form_id, &$form_state) { +function devel_generate_menu_form_submit($form, &$form_state) { // If the create new menus checkbox is off, set the number of new menus to 0. if (!isset($form_state['values']['existing_menus']['__new-menu__']) || !$form_state['values']['existing_menus']['__new-menu__']) { $form_state['values']['num_menus'] = 0; } module_load_include('inc', 'devel_generate'); - devel_generate_menu_data($form_state['values']['num_menus'], $form_state['values']['existing_menus'], $form_state['values']['num_links'], $form_state['values']['title_length'], $form_state['values']['link_types'], $form_state['values']['max_depth'], $form_state['values']['max_width'], $form_state['values']['kill']); + // Delete custom menus. + if ($form_state['values']['kill']) { + devel_generate_delete_menus(); + drupal_set_message(t('Deleted existing menus and links.')); + } + + // Generate new menus. + $new_menus = devel_generate_menus($form_state['values']['num_menus'], $form_state['values']['title_length']); + if (!empty($new_menus)) { + drupal_set_message(t('Created the following new menus: !menus', array('!menus' => implode(', ', $new_menus)))); + } + + // Generate new menu links. + $menus = $new_menus + $form_state['values']['existing_menus']; + $new_links = devel_generate_links($form_state['values']['num_links'], $menus, $form_state['values']['title_length'], $form_state['values']['link_types'], $form_state['values']['max_depth'], $form_state['values']['max_width']); + drupal_set_message(t('Created @count new menu links.', array('@count' => count($new_links)))); } diff --git a/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.test b/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.test new file mode 100644 index 0000000..c649f02 --- /dev/null +++ b/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.test @@ -0,0 +1,108 @@ + t('Devel Generate'), + 'description' => t('Tests the logic to generate data.'), + 'group' => t('Devel'), + ); + } + + /** + * Prepares the testing environment + */ + function setUp() { + parent::setUp(array('devel', 'devel_generate', 'taxonomy', 'menu', 'comment')); + + // Create Basic page and Article node types. + if ($this->profile != 'standard') { + $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic Page')); + } + } + + /** + * Tests generate commands + */ + public function testGenerate() { + $user = $this->drupalCreateUser(array( + 'administer taxonomy', + 'administer menu', + 'administer nodes', + )); + $this->drupalLogin($user); + + // Generate taxonomy vocabularies. + $edit = array( + 'num_vocabs' => 5, + 'title_length' => 12, + 'kill_taxonomy' => 1, + ); + $this->drupalPost('admin/config/development/generate/vocabs', + $edit, t('Generate')); + $this->assertText(t('Deleted existing vocabularies.')); + $this->assertText(t('Created the following new vocabularies:')); + + // Generate taxonomy terms. + $form = devel_generate_term_form(); + $vids = array_keys($form['vids']['#options']); + $edit = array( + 'vids[]' => $vids, + 'num_terms' => 5, + 'title_length' => 12, + 'kill_taxonomy' => 1, + ); + $this->drupalPost('admin/config/development/generate/taxonomy', + $edit, t('Generate')); + $this->assertText(t('Deleted existing terms.')); + $this->assertText(t('Created the following new terms: ')); + + // Generate menus. + $edit = array( + 'existing_menus[__new-menu__]' => 1, + 'num_menus' => 2, + 'num_links' => 50, + 'title_length' => 12, + 'link_types[node]' => 1, + 'link_types[front]' => 1, + 'link_types[external]' => 1, + 'max_depth' => 4, + 'max_width' => 6, + 'kill' => 1, + ); + $this->drupalPost('admin/config/development/generate/menu', + $edit, t('Generate')); + $this->assertText(t('Deleted existing menus and links.')); + $this->assertText(t('Created the following new menus:')); + $this->assertText(t('Created 50 new menu links.')); + + // Generate content. + // First we create a node in order to test the Delete content checkbox. + $this->drupalCreateNode(array()); + + // Now submit the generate content form. + $edit = array( + 'node_types[page]' => 1, + 'kill_content' => 1, + 'num_nodes' => 2, + 'time_range' => 604800, + 'max_comments' => 3, + 'title_length' => 4, + ); + $this->drupalPost('admin/config/development/generate/content', $edit, t('Generate')); + $this->assertText(t('Deleted 1 nodes.')); + $this->assertText(t('Finished creating 2 nodes')); + } +} diff --git a/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate_batch.inc b/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate_batch.inc index 2e90357..61ccc4d 100644 --- a/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate_batch.inc +++ b/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate_batch.inc @@ -1,30 +1,29 @@ t('Generating Content'), 'operations' => $operations, @@ -34,10 +33,6 @@ function devel_generate_batch_content($form_state) { batch_set($batch); } -/** - * Create Content Batch Functions: - */ - function devel_generate_batch_content_kill(&$context) { module_load_include('inc', 'devel_generate', 'devel_generate'); devel_generate_content_kill($context['results']); @@ -50,12 +45,18 @@ function devel_generate_batch_content_pre_node($vars, &$context) { devel_generate_content_pre_node($context['results']); } +/** + * Batch API callback: Generate nodes. + */ function devel_generate_batch_content_add_node(&$context) { module_load_include('inc', 'devel_generate', 'devel_generate'); devel_generate_content_add_node($context['results']); $context['results']['num_nids'] ++; } +/** + * Display a message when a batch is complete. + */ function devel_generate_batch_finished($success, $results, $operations) { if ($success) { $message = t('Finished @num_nids nodes created successfully.', array('@num_nids' => $results['num_nids'])); @@ -65,4 +66,3 @@ function devel_generate_batch_finished($success, $results, $operations) { } drupal_set_message($message); } - diff --git a/sites/all/modules/contrib/dev/devel/devel_generate/file.devel_generate.inc b/sites/all/modules/contrib/dev/devel/devel_generate/file.devel_generate.inc index 84ed5ed..1e78698 100644 --- a/sites/all/modules/contrib/dev/devel/devel_generate/file.devel_generate.inc +++ b/sites/all/modules/contrib/dev/devel/devel_generate/file.devel_generate.inc @@ -14,15 +14,22 @@ function _file_devel_generate($object, $field, $instance, $bundle) { if (empty($file)) { if ($path = devel_generate_textfile()) { - $source = new stdClass(); - $source->uri = $path; - $source->uid = 1; // TODO: randomize? use case specific. - $source->filemime = 'text/plain'; - $source->filename = array_pop(explode("//", $path)); $destination_dir = $field['settings']['uri_scheme'] . '://' . $instance['settings']['file_directory']; file_prepare_directory($destination_dir, FILE_CREATE_DIRECTORY); - $destination = $destination_dir . '/' . basename($path); - $file = file_move($source, $destination, FILE_CREATE_DIRECTORY); + if ($uri = file_unmanaged_move($path, $destination_dir)) { + $file = new stdClass(); + $file->fid = NULL; + $file->uri = $uri; + $file->filename = drupal_basename($uri); + $file->filemime = file_get_mimetype($file->uri); + // @todo Randomize file owner. + $file->uid = 1; + $file->devel_generate = TRUE; + $file = file_save($file); + } + else { + return FALSE; + } } else { return FALSE; diff --git a/sites/all/modules/contrib/dev/devel/devel_generate/image.devel_generate.inc b/sites/all/modules/contrib/dev/devel/devel_generate/image.devel_generate.inc index df1705c..faa9a1d 100644 --- a/sites/all/modules/contrib/dev/devel/devel_generate/image.devel_generate.inc +++ b/sites/all/modules/contrib/dev/devel/devel_generate/image.devel_generate.inc @@ -23,22 +23,28 @@ function _image_devel_generate($object, $field, $instance, $bundle) { $min_resolution = empty($instance['settings']['min_resolution']) ? '100x100' : $instance['settings']['min_resolution']; $max_resolution = empty($instance['settings']['max_resolution']) ? '600x600' : $instance['settings']['max_resolution']; - $extensions = array_intersect(explode(' ', $instance['settings']['file_extensions']), array('png', 'jpg')); + $extensions = array_intersect(explode(' ', $instance['settings']['file_extensions']), array('png', 'gif', 'jpg', 'jpeg')); $extension = array_rand(drupal_map_assoc($extensions)); // Generate a max of 5 different images. if (!isset($images[$extension][$min_resolution][$max_resolution]) || count($images[$extension][$min_resolution][$max_resolution]) <= DEVEL_GENERATE_IMAGE_MAX) { if ($path = devel_generate_image($extension, $min_resolution, $max_resolution)) { - $source = new stdClass(); - $source->uri = $path; - $source->uid = 1; // TODO: randomize? Use case specific. - $source->filemime = 'image/' . pathinfo($path, PATHINFO_EXTENSION); - $source->filename = array_pop(explode("//", $path)); $destination_dir = $field['settings']['uri_scheme'] . '://' . $instance['settings']['file_directory']; file_prepare_directory($destination_dir, FILE_CREATE_DIRECTORY); - $destination = $destination_dir . '/' . basename($path); - $file = file_move($source, $destination, FILE_CREATE_DIRECTORY); - $images[$extension][$min_resolution][$max_resolution][$file->fid] = $file; + if ($uri = file_unmanaged_move($path, $destination_dir)) { + $file = new stdClass(); + $file->fid = NULL; + $file->uri = $uri; + $file->filename = drupal_basename($uri); + $file->filemime = file_get_mimetype($file->uri); + // @todo Randomize file owner. + $file->uid = 1; + $file = file_save($file); + $images[$extension][$min_resolution][$max_resolution][$file->fid] = $file; + } + else { + return FALSE; + } } else { return FALSE; @@ -89,8 +95,6 @@ function devel_generate_image($extension = 'png', $min_resolution, $max_resoluti $save_function = 'image'. ($extension == 'jpg' ? 'jpeg' : $extension); $save_function($im, drupal_realpath($destination)); - - $images[$extension][$min_resolution][$max_resolution][$destination] = $destination; } return $destination; } diff --git a/sites/all/modules/contrib/dev/devel/devel_generate/text.devel_generate.inc b/sites/all/modules/contrib/dev/devel/devel_generate/text.devel_generate.inc index 1f4691d..dedbcd5 100644 --- a/sites/all/modules/contrib/dev/devel/devel_generate/text.devel_generate.inc +++ b/sites/all/modules/contrib/dev/devel/devel_generate/text.devel_generate.inc @@ -19,20 +19,17 @@ function _text_devel_generate($object, $field, $instance, $bundle) { $format = filter_fallback_format(); } - if ($instance['widget']['type'] != 'text_textfield') { + if (empty($field['settings']['max_length'])) { // Textarea handling - $object_field['value'] = devel_create_content($format); + $object_field['value'] = devel_create_content(); if ($instance['widget']['type'] == 'text_textarea_with_summary' && !empty($instance['display_summary'])) { - $object_field['summary'] = devel_create_content($format); + $object_field['summary'] = devel_create_content(); } } else { // Textfield handling. - // Generate a value that respects max_length. - if (empty($field['settings']['max_length'])) { - $field['settings']['max_length'] = 12; - } - $object_field['value'] = user_password($field['settings']['max_length']); + $max = ceil($field['settings']['max_length'] / 6); + $object_field['value'] = substr(devel_create_greeking(mt_rand(1, $max), FALSE), 0, $field['settings']['max_length']); } $object_field['format'] = $format; return $object_field; diff --git a/sites/all/modules/contrib/dev/devel/devel_krumo.css b/sites/all/modules/contrib/dev/devel/devel_krumo.css new file mode 100644 index 0000000..97018fd --- /dev/null +++ b/sites/all/modules/contrib/dev/devel/devel_krumo.css @@ -0,0 +1,11 @@ +/** + * Krumo + */ +.krumo-version, +.krumo-call { + white-space: nowrap; +} + +.krumo-nest { + display: none; +} diff --git a/sites/all/modules/contrib/dev/devel/devel_krumo_path.js b/sites/all/modules/contrib/dev/devel/devel_krumo.js similarity index 71% rename from sites/all/modules/contrib/dev/devel/devel_krumo_path.js rename to sites/all/modules/contrib/dev/devel/devel_krumo.js index fa01f77..e55780a 100644 --- a/sites/all/modules/contrib/dev/devel/devel_krumo_path.js +++ b/sites/all/modules/contrib/dev/devel/devel_krumo.js @@ -1,13 +1,21 @@ +/** + * @file + * Behaviors for Devel. + */ + (function ($) { /** * Attaches double-click behavior to toggle full path of Krumo elements. + * + * @type {Drupal~behavior} */ Drupal.behaviors.devel = { attach: function (context, settings) { + // Path // Add hint to footnote - $('.krumo-footnote .krumo-call').before(''); + $('.krumo-footnote .krumo-call', context).once().before(''); var krumo_name = []; var krumo_type = []; @@ -21,7 +29,9 @@ Drupal.behaviors.devel = { } } - $('.krumo-child > div:first-child', context).dblclick( + $('.krumo-child > div:first-child', context).once('krumo_path', + function() { + $(this).dblclick( function(e) { if ($(this).find('> .krumo-php-path').length > 0) { // Remove path if shown. @@ -58,9 +68,20 @@ Drupal.behaviors.devel = { krumo_name = []; krumo_type = []; } - } - ); - } -}; + }); + }); -})(jQuery); + // Events + $('.krumo-element').once('krumo-events', function() { + $(this).click(function() { + krumo.toggle(this); + }).mouseover(function() { + krumo.over(this); + }).mouseout(function() { + krumo.out(this); + }); + });// End krumo-events .once + }// End attach. +};// End behaviors.devel. + +})(jQuery);// end outer function diff --git a/sites/all/modules/contrib/dev/devel/devel_node_access.api.php b/sites/all/modules/contrib/dev/devel/devel_node_access.api.php index aacf9a6..79265ff 100644 --- a/sites/all/modules/contrib/dev/devel/devel_node_access.api.php +++ b/sites/all/modules/contrib/dev/devel/devel_node_access.api.php @@ -13,19 +13,19 @@ /** * Explain your records in the {node_access} table. * - * In order to help developers and administrators understand the forces - * that control access to any given node, the DNA module provides the + * In order to help developers and administrators understand the forces that + * control access to any given node, the DNA module provides the * Devel Node Access block, which lists all the grant records in the * {node_access} table for that node. * * However, every Node Access module is free in how it defines and uses the - * 'realm' and 'gid' fields in its records in the {node_access} table, and - * it's often difficult to interpret them. This hook passes each record - * that DNA wants to display, and the owning module is expected to return - * an explanation of that record. + * 'realm' and 'gid' fields in its records in the {node_access} table, and it's + * often difficult to interpret them. This hook passes each record that DNA + * wants to display, and the owning module is expected to return an explanation + * of that record. * - * The explanation should not be localized (not be passed through t()), so - * that administrators seeking help can present English explanations. + * The explanation should not be localized (not be passed through t()), so that + * administrators seeking help can present English explanations. * * @param $row * The record from the {node_access} table, as object. The member fields are: diff --git a/sites/all/modules/contrib/dev/devel/devel_node_access.info b/sites/all/modules/contrib/dev/devel/devel_node_access.info index 07195cc..bd790b2 100644 --- a/sites/all/modules/contrib/dev/devel/devel_node_access.info +++ b/sites/all/modules/contrib/dev/devel/devel_node_access.info @@ -6,9 +6,8 @@ core = 7.x configure = admin/config/development/devel tags[] = developer -; Information added by drupal.org packaging script on 2012-06-05 -version = "7.x-1.3" +; Information added by Drupal.org packaging script on 2019-02-22 +version = "7.x-1.7" core = "7.x" project = "devel" -datestamp = "1338940281" - +datestamp = "1550852892" diff --git a/sites/all/modules/contrib/dev/devel/devel_node_access.install b/sites/all/modules/contrib/dev/devel/devel_node_access.install index 0088d78..5412045 100644 --- a/sites/all/modules/contrib/dev/devel/devel_node_access.install +++ b/sites/all/modules/contrib/dev/devel/devel_node_access.install @@ -1,5 +1,10 @@ array( @@ -51,7 +54,7 @@ function devel_node_access_menu() { if (!module_exists('devel')) { if (!menu_load('devel')) { - // we have to create the 'devel' menu ourselves + // We have to create the 'devel' menu ourselves. $menu = array( 'menu_name' => 'devel', 'title' => 'Development', @@ -60,10 +63,10 @@ function devel_node_access_menu() { menu_save($menu); } - // we have to create the 'Devel settings' menu item ourselves + // We have to create the 'Devel settings' menu item ourselves. $items['admin/config/development/devel'] = array( 'title' => 'Devel settings', - 'description' => 'Helper pages and blocks to assist Drupal developers and admins with node_access. The devel blocks can be managed via the block administration page.', + 'description' => 'Helper pages and blocks to assist Drupal developers and admins with node_access. The devel blocks can be managed via the block administration page.', 'page callback' => 'drupal_get_form', 'page arguments' => array('devel_node_access_admin_settings'), 'access arguments' => array('administer site configuration'), @@ -73,7 +76,7 @@ function devel_node_access_menu() { ); } - // create a callback for use by devel_node_access_user_ajax(). + // Create a callback for use by devel_node_access_user_ajax(). $items['devel/node_access/by_user/%/%'] = array( 'page callback' => 'devel_node_access_user_ajax', 'page arguments' => array(3, 4), @@ -81,7 +84,7 @@ function devel_node_access_menu() { 'type' => MENU_CALLBACK, ); - // add this to the custom menu 'devel' created by the devel module. + // Add this to the custom menu 'devel' created by the devel module. $items['devel/node_access/summary'] = array( 'title' => 'Node_access summary', 'page callback' => 'dna_summary', @@ -92,11 +95,24 @@ function devel_node_access_menu() { return $items; } +/** + * Form constructor for Devel node access settings. + * + * @see devel_node_access_form_alter() + * + * @ingroup forms + */ function devel_node_access_admin_settings() { $form = array(); return system_settings_form($form); } +/** + * Implements hook_form_alter(). + * + * Adds form elements to devel_admin_settings() and + * devel_node_access_admin_settings(). + */ function devel_node_access_form_alter(&$form, $form_state, $form_id) { $tr = 't'; if ($form_id == 'devel_admin_settings' || $form_id == 'devel_node_access_admin_settings') { @@ -126,6 +142,9 @@ function devel_node_access_form_alter(&$form, $form_state, $form_id) { } } +/** + * Page callback for the Devel node access summary. + */ function dna_summary() { // warn user if they have any entries that could grant access to all nodes $output = ''; @@ -221,23 +240,47 @@ function dna_summary() { return $output; } +/** + * Retreives the nodes visible in the current menu entry. + * + * @param integer $nid + * (optional) A node ID to add to the list. + * + * @return array + * An associative array of Node ID's that this function has been called with + * keyed by Node ID. + */ function dna_visible_nodes($nid = NULL) { static $nids = array(); if ($nid) { $nids[$nid] = $nid; } - elseif (empty($nids) && arg(0) == 'node' && is_numeric(arg(1)) && arg(2) == NULL) { - // show DNA information on node/NID even if access is denied (IF the user has the 'view devel_node_access information' permission)! - return array(arg(1)); + elseif (empty($nids)) { + $menu_item = menu_get_item(); + $map = $menu_item['original_map']; + if ($map[0] == 'node' && isset($map[1]) && is_numeric($map[1]) && !isset($map[2])) { + // show DNA information on node/NID even if access is denied (IF the user has the 'view devel_node_access information' permission)! + return array($map[1]); + } } return $nids; } +/** + * Implements hook_node_view(). + */ function devel_node_access_node_view($node, $build_mode) { // remember this node, for display in our block dna_visible_nodes($node->nid); } +/** + * Invokes a hook. + * + * @return + * The results of the hook invocation keyed by the machine name of the + * implementing module. + */ function _devel_node_access_module_invoke_all() { // array and scalar returns $args = func_get_args(); $hook = $args[0]; @@ -250,12 +293,12 @@ function _devel_node_access_module_invoke_all() { // array and scalar returns if (isset($result)) { if (is_array($result)) { foreach ($result as $key => $value) { - // add name of module that returned the value: + // Add name of module that returned the value. $result[$key]['#module'] = $module; } } else { - // build array with result keyed by $module: + // Build array with result keyed by $module. $result = array($module => $result); } $return = array_merge_recursive($return, $result); @@ -266,12 +309,13 @@ function _devel_node_access_module_invoke_all() { // array and scalar returns } /** - * Helper function to build an associative array of grant records and their - * history. If there are duplicate records, display an error message. + * Builds an associative array of grant records and their history. + * + * If there are duplicate records, display an error message. * * @param $grants - * An indexed array of grant records, augmented by the '#module' key, - * as created by _devel_node_access_module_invoke_all('node_access_records'). + * An indexed array of grant records, augmented by the '#module' key, as + * created by _devel_node_access_module_invoke_all('node_access_records'). * * @param $node * The node that the grant records belong to. @@ -317,8 +361,7 @@ function _devel_node_access_build_nar_data($grants, $node, $function) { } /** - * Helper function to mimic hook_node_access_records_alter() and trace what - * each module does with it. + * Mimics hook_node_access_records_alter() and traces what each module does with it. * * @param object $grants * An indexed array of grant records, augmented by the '#module' key, @@ -346,18 +389,19 @@ function _devel_node_access_nar_alter(&$grants, $node) { isset($drupal_static['drupal_alter']) || ($drupal_static['drupal_alter'] = &drupal_static('drupal_alter')); $functions = $drupal_static['drupal_alter']; - // build the initial tree (and check for duplicates) + // Build the initial tree (and check for duplicates). $data = _devel_node_access_build_nar_data($grants, $node, 'hook_node_access_records()'); - // simulate drupal_alter('node_access_records', $grants, $node); + // Simulate drupal_alter('node_access_records', $grants, $node); foreach ($functions['node_access_records'] as $function) { - // call hook_node_access_records_alter() for one module at a time and analyze + // Call hook_node_access_records_alter() for one module at a time and + // analyze. $function($grants, $node); // <== $module = substr($function, 0, strlen($function) - 26); foreach ($grants as $i => $grant) { if (empty($data[$grant['realm']][$grant['gid']]['current'])) { - // it's an added grant + // It's an added grant. $data[$grant['realm']][$grant['gid']]['current'] = $grant; $data[$grant['realm']][$grant['gid']]['current']['#module'] = $module; $data[$grant['realm']][$grant['gid']]['changes'][] = array( @@ -367,13 +411,14 @@ function _devel_node_access_nar_alter(&$grants, $node) { $grants[$i]['#module'] = $module; } else { - // it's an existing grant, check for changes + // It's an existing grant, check for changes. + $view = $update = $delete = FALSE; foreach (array('view', 'update', 'delete') as $op) { $$op = $grant["grant_$op"] - $data[$grant['realm']][$grant['gid']]['current']["grant_$op"]; } $priority = $grant['priority'] - $data[$grant['realm']][$grant['gid']]['current']['priority']; if ($view || $update || $delete || $priority) { - // it was changed + // It was changed. $data[$grant['realm']][$grant['gid']]['current'] = $grant; $data[$grant['realm']][$grant['gid']]['current']['#module'] = $module; $data[$grant['realm']][$grant['gid']]['changes'][] = array( @@ -386,10 +431,10 @@ function _devel_node_access_nar_alter(&$grants, $node) { $data[$grant['realm']][$grant['gid']]['found'] = TRUE; } - // check for newly introduced duplicates + // Check for newly introduced duplicates. _devel_node_access_build_nar_data($grants, $node, 'hook_node_access_records_alter()'); - // look for grant records that have disappeared + // Look for grant records that have disappeared. foreach ($data as $realm => $data2) { foreach ($data2 as $gid => $data3) { if (empty($data[$realm][$gid]['found']) && isset($data[$realm][$gid]['current'])) { @@ -408,14 +453,12 @@ function _devel_node_access_nar_alter(&$grants, $node) { } /** - * Helper function to mimic hook_node_grants_alter() and trace what - * each module does with it. + * Mimics hook_node_grants_alter() and traces what each module does with it. * * @param object $grants - * An indexed array of grant records, augmented by the '#module' key, - * as created by _devel_node_access_module_invoke_all('node_grants'). - * This array is updated by the hook_node_grants_alter() - * implementations. + * An indexed array of grant records, augmented by the '#module' key, as + * created by _devel_node_access_module_invoke_all('node_grants'). This array + * is updated by the hook_node_grants_alter() implementations. * * @param $node * The node that the grant records belong to. @@ -424,12 +467,13 @@ function _devel_node_access_nar_alter(&$grants, $node) { * A tree representation of the grant records in $grants including their * history: * $data[$realm][$gid] - * ['cur'] - TRUE or FALSE whether the gid is present or not - * ['ori'][] - array of module names that contributed this grant (if any) - * ['chg'][] - array of changes, such as - * - 'added' if module name is a prefix if the $realm, - * - 'added by module' otherwise, or - * - 'removed by module' + * - ['cur']: TRUE or FALSE whether the gid is present or not + * - ['ori'][]: An array of module names that contributed this grant (if + * any) + * - ['chg'][]: An array of changes, such as + * - 'added' if module name is a prefix if the $realm, + * - 'added by module' otherwise, or + * - 'removed by module' */ function _devel_node_access_ng_alter(&$grants, $account, $op) { //dpm($grants, '_devel_node_access_ng_alter(): grants IN'); @@ -439,7 +483,7 @@ function _devel_node_access_ng_alter(&$grants, $account, $op) { isset($drupal_static['drupal_alter']) || ($drupal_static['drupal_alter'] = &drupal_static('drupal_alter')); $functions = $drupal_static['drupal_alter']; - // build the initial structure + // Build the initial structure. $data = array(); foreach ($grants as $realm => $gids) { foreach ($gids as $i => $gid) { @@ -451,13 +495,13 @@ function _devel_node_access_ng_alter(&$grants, $account, $op) { unset($grants[$realm]['#module']); } - // simulate drupal_alter('node_grants', $grants, $account, $op); + // Simulate drupal_alter('node_grants', $grants, $account, $op); foreach ($functions['node_grants'] as $function) { - // call hook_node_grants_alter() for one module at a time and analyze + // Call hook_node_grants_alter() for one module at a time and analyze. $function($grants, $account, $op); // <== $module = substr($function, 0, strlen($function) - 18); - // check for new gids + // Check for new gids. foreach ($grants as $realm => $gids) { foreach ($gids as $i => $gid) { if (empty($data[$realm][$gid]['cur'])) { @@ -467,7 +511,7 @@ function _devel_node_access_ng_alter(&$grants, $account, $op) { } } - // check for removed gids + // Check for removed gids. foreach ($data as $realm => $gids) { foreach ($gids as $gid => $history) { if ($history['cur'] && array_search($gid, $grants[$realm]) === FALSE) { @@ -496,6 +540,7 @@ function devel_node_access_block_info() { $blocks['dna_user'] = array( 'info' => t('Devel Node Access by User'), 'region' => 'footer', + 'status' => 0, 'cache' => DRUPAL_NO_CACHE, ); return $blocks; @@ -592,9 +637,9 @@ function devel_node_access_block_view($delta) { foreach ($nids as $nid) { $acquired_grants_nid = array(); if ($node = node_load($nid)) { - // check node_access_acquire_grants() + // Check node_access_acquire_grants(). $grants = _devel_node_access_module_invoke_all('node_access_records', $node); - // check drupal_alter('node_access_records') + // Check drupal_alter('node_access_records'). $data = _devel_node_access_nar_alter($grants, $node); /* (This was the D6 implementation that didn't analyze the hook_node_access_records_alter() details.) if (!empty($grants)) { @@ -639,19 +684,20 @@ function devel_node_access_block_view($delta) { } /**/ //dpm($acquired_grants_nid, "acquired_grants_nid ="); - // check node_access_grants() + // Check node_access_grants(). $published_nid[$nid] = $node->status; if ($node->nid) { foreach (array('view', 'update', 'delete') as $op) { $grants = _devel_node_access_module_invoke_all('node_grants', $user, $op); - // call all hook_node_grants_alter() implementations - $ng_alter_data = _devel_node_access_ng_alter($grants, $user, $op); + // Call all hook_node_grants_alter() implementations. + $ng_alter_datas[$op] = _devel_node_access_ng_alter($grants, $user, $op); $checked_grants[$nid][$op] = array_merge(array('all' => array(0)), $grants); } } } - // check for grants in the node_access table that aren't returned by node_access_acquire_grants() + // Check for grants in the node_access table that aren't returned by + // node_access_acquire_grants(). if (isset($active_grants[$nid])) { foreach ($active_grants[$nid] as $realm => $active_grants_realm) { @@ -680,14 +726,15 @@ function devel_node_access_block_view($delta) { elseif (!$found) { $acknowledged = _devel_node_access_module_invoke_all('node_access_acknowledge', $fixed_grant); if (empty($acknowledged)) { - // no module acknowledged this record, mark it as alien + // No module acknowledged this record, mark it as alien. $fixed_grant += array( 'priority' => '?', 'state' => 'alien', ); } else { - // at least one module acknowledged the record, attribute it to the first one + // At least one module acknowledged the record. Attribute it + // to the first one. $acknowledged_keys = array_keys($acknowledged); $fixed_grant += array( 'priority' => '–', @@ -708,7 +755,7 @@ function devel_node_access_block_view($delta) { } } - // order grants and evaluate their status + // Order grants and evaluate their status. foreach ($acquired_grants_nid as $priority => $acquired_grants_priority) { ksort($acquired_grants_priority); foreach ($acquired_grants_priority as $realm => $acquired_grants_realm) { @@ -744,7 +791,7 @@ function devel_node_access_block_view($delta) { } } - // fill in the table rows + // Fill in the table rows. $rows = array(); $error_count = 0; foreach ($all_grants as $grant) { @@ -788,9 +835,17 @@ function devel_node_access_block_view($delta) { 'data' => '' . $row['nid'] . '', 'title' => $grant['#title'], ); - $row['realm'] = (empty($grant['#module']) || strpos($grant['realm'], $grant['#module']) === 0 ? '' : $grant['#module'] . ':
    ') . $grant['realm']; + if (empty($grant['#module']) || strpos($grant['realm'], $grant['#module']) === 0) { + $row['realm'] = $grant['realm']; + } + else { + $row['realm'] = array( + 'data' => $grant['#module'] . ':
    ' . $grant['realm'], + 'title' => t("The '@module' module fails to adhere to the best practice of naming its realm(s) after itself.", array('@module' => $grant['#module'])), + ); + } - // prepend information from the D7 hook_node_access_records_alter() + // Prepend information from the D7 hook_node_access_records_alter(). $next_style = array(); if (isset($grant['history'])) { $history = $grant['history']; @@ -831,14 +886,15 @@ function devel_node_access_block_view($delta) { } } - // fix up the main row cells with the proper class (needed for Bartik) + // Fix up the main row cells with the proper class (needed for + // Bartik). foreach ($row as $key => $value) { if (!is_array($value)) { $row[$key] = array('data' => $value); } $row[$key]['class'] = array($class); } - // add the main row + // Add the main row. $will_append = empty($history['current']) && !empty($history['changes']); $rows[] = array( 'data' => array_values($row), @@ -846,7 +902,7 @@ function devel_node_access_block_view($delta) { 'style' => array_merge($next_style, ($will_append ? array('border-bottom-style: none;') : array())), ); - // append information from the D7 hook_node_access_records_alter() + // Append information from the D7 hook_node_access_records_alter(). if ($will_append) { $last_change = end($history['changes']); $rows[] = array( @@ -889,7 +945,8 @@ function devel_node_access_block_view($delta) { ); } - // Explain whether access is granted or denied, and why (using code from node_access()). + // Explain whether access is granted or denied, and why (using code from + // node_access()). $tr = 't'; array_shift($nids); // remove the 0 $accounts = array(); @@ -906,7 +963,7 @@ function devel_node_access_block_view($delta) { ); } else { - $variables['!list'] = '

    ' . _devel_node_access_get_grant_list($nid, $ng_alter_data) . '
    '; + $variables['!list'] = '
    ' . _devel_node_access_get_grant_list($nid, $ng_alter_datas['view']) . '
    '; $variables['%access'] = 'view'; $output[] = array( '#prefix' => "\n
    ", @@ -915,7 +972,10 @@ function devel_node_access_block_view($delta) { ); $accounts[] = $user; } - if (arg(0) == 'node' && is_numeric(arg(1)) && !$block1_visible) { // only for single nodes + $menu_item = menu_get_item(); + $map = $menu_item['original_map']; + if ($map[0] == 'node' && isset($map[1]) && is_numeric($map[1]) && !isset($map[2]) && !$block1_visible) { + // Only for single nodes. if (user_is_logged_in()) { $accounts[] = user_load(0); // Anonymous, too } @@ -948,10 +1008,11 @@ function devel_node_access_block_view($delta) { '#suffix' => '
    ', ); } - $variables['!username'] = theme('username', array('account' => $account)); + $variables['!username'] = '' . theme('username', array('account' => $account)) . ''; $output[] = array( '#prefix' => "\n
    ", - '#markup' => t("!username has the following access", $variables), + '#type' => 'item', + 'lead-in' => array('#markup' => t("!username has the following access", $variables) . ' '), 'items' => $account_items, '#suffix' => "\n
    \n", ); @@ -970,10 +1031,29 @@ function devel_node_access_block_view($delta) { return array('subject' => $subject, 'content' => $output); case 'dna_user': - // show which users can access this node - if (arg(0) == 'node' && is_numeric($nid = arg(1)) && arg(2) == NULL && $node = node_load($nid)) { + // Show which users can access this node. + $menu_item = menu_get_item(); + $map = $menu_item['original_map']; + if ($map[0] != 'node' || !isset($map[1]) || !is_numeric($map[1]) || isset($map[2])) { + // Ignore anything but node/%. + return; + } + + if (isset($menu_item['map'][1]) && is_object($node = $menu_item['map'][1])) { + // We have the node. + } + elseif (is_numeric($menu_item['original_map'][1])) { + $node = node_load($menu_item['original_map'][1]); + } + if (isset($node)) { + $nid = $node->nid; $node_type = node_type_get_type($node); - $headers = array(t('username'), ' $node_type->name)) . '">' . t('create') . '', t('view'), t('update'), t('delete')); + $variables = array('@Node_type' => ($node_type ? $node_type->name : $node->type)); + $create_header = '' . t('create') . ''; + if (!$node_type) { + $create_header .= '
    ' . t("(missing type: '@Node_type')", $variables) . ''; + } + $headers = array(t('username'), $create_header, t('view'), t('update'), t('delete')); $rows = array(); // Determine whether to use Ajax or prepopulate the tables. if ($ajax = variable_get('devel_node_access_user_ajax', FALSE)) { @@ -1042,7 +1122,7 @@ function devel_node_access_block_view($delta) { } /** - * Callback function for node access by user block ajax. + * Page callback for node access by user block ajax. */ function devel_node_access_user_ajax($data_type, $cell) { list($op, $nid, $uid) = explode('-', $cell); @@ -1080,7 +1160,8 @@ function devel_node_access_user_ajax($data_type, $cell) { /** * Helper function that mimicks node.module's node_access() function. * - * Unfortunately, this needs to be updated manually whenever node.module changes! + * Unfortunately, this needs to be updated manually whenever node.module + * changes! * * @return * An array suitable for theming with theme_dna_permission(). @@ -1220,7 +1301,13 @@ function _devel_node_access_explain_access($op, $node, $account = NULL) { } /** - * Helper function to create a list of the grants returned by hook_node_grants(). + * Creates a list of the grants returned by hook_node_grants(). + * + * @param integer $nid + * @param array $ng_alter_data + * + * @return + * A themed item list of grants. */ function _devel_node_access_get_grant_list($nid, $ng_alter_data) { //dpm($ng_alter_data, "_devel_node_access_get_grant_list($nid,"); @@ -1286,7 +1373,16 @@ function devel_node_access_node_access_explain($row) { } /** - * Helper function to return a sanitized node title. + * Return a sanitized node title. + * + * @param $node + * The node from which to get the title. + * @param boolean $clip_and_decorate + * (optional) Set to TRUE to limit the length of the returned title and to add + * markup to make it easier to style. + * + * @return string + * The sanitized node title. */ function _devel_node_access_get_node_title($node, $clip_and_decorate = FALSE) { if (isset($node)) { @@ -1308,7 +1404,13 @@ function _devel_node_access_get_node_title($node, $clip_and_decorate = FALSE) { } /** - * Helper function to apply common formatting to a debug-mode table row. + * Applies common formatting to a debug-mode table row. + * + * @param array $row + * The row to format. + * @param boolean $may_unpack + * (optional) Indicates if $row['data'] needs be formatted rather than $row + * itself. Default is TRUE. */ function _devel_node_access_format_row($row, $may_unpack = TRUE) { if ($may_unpack && isset($row['data'])) { @@ -1348,7 +1450,14 @@ function devel_node_access_theme() { } /** - * Indicate whether user has a permission or not. + * Returns HTML to indicate if a user has a permission. + * + * @param array $variables + * An associative array containing: + * - permission: An indexed array containing: + * - 0: A value indicating if the user has permission. + * - 1: The text of the message to display. + * - 2: The title to display containing additional information. */ function theme_dna_permission($variables) { $permission = &$variables['permission']; diff --git a/sites/all/modules/contrib/dev/devel/krumo/INSTALL b/sites/all/modules/contrib/dev/devel/krumo/INSTALL old mode 100755 new mode 100644 index e6117c8..9283bc5 --- a/sites/all/modules/contrib/dev/devel/krumo/INSTALL +++ b/sites/all/modules/contrib/dev/devel/krumo/INSTALL @@ -1,32 +1,32 @@ ------------------------------------------------------------------------------- - - SETUP: How to install Krumo ? - ------------------------------------------------------------------------------- - -In order to use Krumo you have to put it on your (development) server, and -include it in your script. You can put it somewhere in the INCLUDE_PATH, or -specify the full path to the "class.krumo.php" file. - -You have to modify the "krumo.ini" file too. It is the configuration file for -Krumo. The first option is choosing a skin: - - [skin] - selected = "orange" - -The value for this setting has to be the name of one of the sub-folders from the -"Krumo/skins/" folder. If the value provided for the skin results in not finding -the skin, the `default` skin will be used instead. - -The second option is used to set the correct web path to the folder where Krumo -is installed. This is used in order to make the images from Krumo's CSS skins -web-accessible. - - [css] - url = "http://www.example.com/Krumo/" - -So far those two are the only configuration options. - -All the CSS files ("skin.css") from the "Krumo/skins/" sub-folders must have the -proper permissions in order to be readable from Krumo. Same applies for -"krumo.ini" and "krumo.js" files. \ No newline at end of file +------------------------------------------------------------------------------ + + SETUP: How to install Krumo ? + +------------------------------------------------------------------------------ + +In order to use Krumo you have to put it on your (development) server, and +include it in your script. You can put it somewhere in the INCLUDE_PATH, or +specify the full path to the "class.krumo.php" file. + +You have to modify the "krumo.ini" file too. It is the configuration file for +Krumo. The first option is choosing a skin: + + [skin] + selected = "orange" + +The value for this setting has to be the name of one of the sub-folders from the +"Krumo/skins/" folder. If the value provided for the skin results in not finding +the skin, the `default` skin will be used instead. + +The second option is used to set the correct web path to the folder where Krumo +is installed. This is used in order to make the images from Krumo's CSS skins +web-accessible. + + [css] + url = "http://www.example.com/Krumo/" + +So far those two are the only configuration options. + +All the CSS files ("skin.css") from the "Krumo/skins/" sub-folders must have the +proper permissions in order to be readable from Krumo. Same applies for +"krumo.ini" and "krumo.js" files. diff --git a/sites/all/modules/contrib/dev/devel/krumo/LICENSE b/sites/all/modules/contrib/dev/devel/krumo/LICENSE old mode 100755 new mode 100644 index 03851a3..678c692 --- a/sites/all/modules/contrib/dev/devel/krumo/LICENSE +++ b/sites/all/modules/contrib/dev/devel/krumo/LICENSE @@ -1,504 +1,504 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 - - Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - -[This is the first released version of the Lesser GPL. It also counts - as the successor of the GNU Library Public License, version 2, hence - the version number 2.1.] - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -Licenses are intended to guarantee your freedom to share and change -free software--to make sure the software is free for all its users. - - This license, the Lesser General Public License, applies to some -specially designated software packages--typically libraries--of the -Free Software Foundation and other authors who decide to use it. You -can use it too, but we suggest you first think carefully about whether -this license or the ordinary General Public License is the better -strategy to use in any particular case, based on the explanations below. - - When we speak of free software, we are referring to freedom of use, -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 and use pieces of -it in new free programs; and that you are informed that you can do -these things. - - To protect your rights, we need to make restrictions that forbid -distributors to deny you these rights or to ask you to surrender these -rights. These restrictions translate to certain responsibilities for -you if you distribute copies of the library or if you modify it. - - For example, if you distribute copies of the library, whether gratis -or for a fee, you must give the recipients all the rights that we gave -you. You must make sure that they, too, receive or can get the source -code. If you link other code with the library, you must provide -complete object files to the recipients, so that they can relink them -with the library after making changes to the library and recompiling -it. And you must show them these terms so they know their rights. - - We protect your rights with a two-step method: (1) we copyright the -library, and (2) we offer you this license, which gives you legal -permission to copy, distribute and/or modify the library. - - To protect each distributor, we want to make it very clear that -there is no warranty for the free library. Also, if the library is -modified by someone else and passed on, the recipients should know -that what they have is not the original version, so that the original -author's reputation will not be affected by problems that might be -introduced by others. - - Finally, software patents pose a constant threat to the existence of -any free program. We wish to make sure that a company cannot -effectively restrict the users of a free program by obtaining a -restrictive license from a patent holder. Therefore, we insist that -any patent license obtained for a version of the library must be -consistent with the full freedom of use specified in this license. - - Most GNU software, including some libraries, is covered by the -ordinary GNU General Public License. This license, the GNU Lesser -General Public License, applies to certain designated libraries, and -is quite different from the ordinary General Public License. We use -this license for certain libraries in order to permit linking those -libraries into non-free programs. - - When a program is linked with a library, whether statically or using -a shared library, the combination of the two is legally speaking a -combined work, a derivative of the original library. The ordinary -General Public License therefore permits such linking only if the -entire combination fits its criteria of freedom. The Lesser General -Public License permits more lax criteria for linking other code with -the library. - - We call this license the "Lesser" General Public License because it -does Less to protect the user's freedom than the ordinary General -Public License. It also provides other free software developers Less -of an advantage over competing non-free programs. These disadvantages -are the reason we use the ordinary General Public License for many -libraries. However, the Lesser license provides advantages in certain -special circumstances. - - For example, on rare occasions, there may be a special need to -encourage the widest possible use of a certain library, so that it becomes -a de-facto standard. To achieve this, non-free programs must be -allowed to use the library. A more frequent case is that a free -library does the same job as widely used non-free libraries. In this -case, there is little to gain by limiting the free library to free -software only, so we use the Lesser General Public License. - - In other cases, permission to use a particular library in non-free -programs enables a greater number of people to use a large body of -free software. For example, permission to use the GNU C Library in -non-free programs enables many more people to use the whole GNU -operating system, as well as its variant, the GNU/Linux operating -system. - - Although the Lesser General Public License is Less protective of the -users' freedom, it does ensure that the user of a program that is -linked with the Library has the freedom and the wherewithal to run -that program using a modified version of the Library. - - The precise terms and conditions for copying, distribution and -modification follow. Pay close attention to the difference between a -"work based on the library" and a "work that uses the library". The -former contains code derived from the library, whereas the latter must -be combined with the library in order to run. - - GNU LESSER GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License Agreement applies to any software library or other -program which contains a notice placed by the copyright holder or -other authorized party saying it may be distributed under the terms of -this Lesser General Public License (also called "this License"). -Each licensee is addressed as "you". - - A "library" means a collection of software functions and/or data -prepared so as to be conveniently linked with application programs -(which use some of those functions and data) to form executables. - - The "Library", below, refers to any such software library or work -which has been distributed under these terms. A "work based on the -Library" means either the Library or any derivative work under -copyright law: that is to say, a work containing the Library or a -portion of it, either verbatim or with modifications and/or translated -straightforwardly into another language. (Hereinafter, translation is -included without limitation in the term "modification".) - - "Source code" for a work means the preferred form of the work for -making modifications to it. For a library, 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 library. - - Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running a program using the Library is not restricted, and output from -such a program is covered only if its contents constitute a work based -on the Library (independent of the use of the Library in a tool for -writing it). Whether that is true depends on what the Library does -and what the program that uses the Library does. - - 1. You may copy and distribute verbatim copies of the Library's -complete 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 distribute a copy of this License along with the -Library. - - 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 Library or any portion -of it, thus forming a work based on the Library, 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) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices - stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no - charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a - table of data to be supplied by an application program that uses - the facility, other than as an argument passed when the facility - is invoked, then you must make a good faith effort to ensure that, - in the event an application does not supply such function or - table, the facility still operates, and performs whatever part of - its purpose remains meaningful. - - (For example, a function in a library to compute square roots has - a purpose that is entirely well-defined independent of the - application. Therefore, Subsection 2d requires that any - application-supplied function or table used by this function must - be optional: if the application does not supply it, the square - root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Library, -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 Library, 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 Library. - -In addition, mere aggregation of another work not based on the Library -with the Library (or with a work based on the Library) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may opt to apply the terms of the ordinary GNU General Public -License instead of this License to a given copy of the Library. To do -this, you must alter all the notices that refer to this License, so -that they refer to the ordinary GNU General Public License, version 2, -instead of to this License. (If a newer version than version 2 of the -ordinary GNU General Public License has appeared, then you can specify -that version instead if you wish.) Do not make any other change in -these notices. - - Once this change is made in a given copy, it is irreversible for -that copy, so the ordinary GNU General Public License applies to all -subsequent copies and derivative works made from that copy. - - This option is useful when you wish to copy part of the code of -the Library into a program that is not a library. - - 4. You may copy and distribute the Library (or a portion or -derivative of it, under Section 2) in object code or executable form -under the terms of Sections 1 and 2 above provided that you 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. - - If distribution of 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 satisfies the requirement to -distribute the source code, even though third parties are not -compelled to copy the source along with the object code. - - 5. A program that contains no derivative of any portion of the -Library, but is designed to work with the Library by being compiled or -linked with it, is called a "work that uses the Library". Such a -work, in isolation, is not a derivative work of the Library, and -therefore falls outside the scope of this License. - - However, linking a "work that uses the Library" with the Library -creates an executable that is a derivative of the Library (because it -contains portions of the Library), rather than a "work that uses the -library". The executable is therefore covered by this License. -Section 6 states terms for distribution of such executables. - - When a "work that uses the Library" uses material from a header file -that is part of the Library, the object code for the work may be a -derivative work of the Library even though the source code is not. -Whether this is true is especially significant if the work can be -linked without the Library, or if the work is itself a library. The -threshold for this to be true is not precisely defined by law. - - If such an object file uses only numerical parameters, data -structure layouts and accessors, and small macros and small inline -functions (ten lines or less in length), then the use of the object -file is unrestricted, regardless of whether it is legally a derivative -work. (Executables containing this object code plus portions of the -Library will still fall under Section 6.) - - Otherwise, if the work is a derivative of the Library, you may -distribute the object code for the work under the terms of Section 6. -Any executables containing that work also fall under Section 6, -whether or not they are linked directly with the Library itself. - - 6. As an exception to the Sections above, you may also combine or -link a "work that uses the Library" with the Library to produce a -work containing portions of the Library, and distribute that work -under terms of your choice, provided that the terms permit -modification of the work for the customer's own use and reverse -engineering for debugging such modifications. - - You must give prominent notice with each copy of the work that the -Library is used in it and that the Library and its use are covered by -this License. You must supply a copy of this License. If the work -during execution displays copyright notices, you must include the -copyright notice for the Library among them, as well as a reference -directing the user to the copy of this License. Also, you must do one -of these things: - - a) Accompany the work with the complete corresponding - machine-readable source code for the Library including whatever - changes were used in the work (which must be distributed under - Sections 1 and 2 above); and, if the work is an executable linked - with the Library, with the complete machine-readable "work that - uses the Library", as object code and/or source code, so that the - user can modify the Library and then relink to produce a modified - executable containing the modified Library. (It is understood - that the user who changes the contents of definitions files in the - Library will not necessarily be able to recompile the application - to use the modified definitions.) - - b) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (1) uses at run time a - copy of the library already present on the user's computer system, - rather than copying library functions into the executable, and (2) - will operate properly with a modified version of the library, if - the user installs one, as long as the modified version is - interface-compatible with the version that the work was made with. - - c) Accompany the work with a written offer, valid for at - least three years, to give the same user the materials - specified in Subsection 6a, above, for a charge no more - than the cost of performing this distribution. - - d) If distribution of the work is made by offering access to copy - from a designated place, offer equivalent access to copy the above - specified materials from the same place. - - e) Verify that the user has already received a copy of these - materials or that you have already sent this user a copy. - - For an executable, the required form of the "work that uses the -Library" must include any data and utility programs needed for -reproducing the executable from it. However, as a special exception, -the materials to be 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. - - It may happen that this requirement contradicts the license -restrictions of other proprietary libraries that do not normally -accompany the operating system. Such a contradiction means you cannot -use both them and the Library together in an executable that you -distribute. - - 7. You may place library facilities that are a work based on the -Library side-by-side in a single library together with other library -facilities not covered by this License, and distribute such a combined -library, provided that the separate distribution of the work based on -the Library and of the other library facilities is otherwise -permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work - based on the Library, uncombined with any other library - facilities. This must be distributed under the terms of the - Sections above. - - b) Give prominent notice with the combined library of the fact - that part of it is a work based on the Library, and explaining - where to find the accompanying uncombined form of the same work. - - 8. You may not copy, modify, sublicense, link with, or distribute -the Library except as expressly provided under this License. Any -attempt otherwise to copy, modify, sublicense, link with, or -distribute the Library 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. - - 9. 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 Library or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Library (or any work based on the -Library), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Library or works based on it. - - 10. Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the -original licensor to copy, distribute, link with or modify the Library -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 with -this License. - - 11. 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 Library at all. For example, if a patent -license would not permit royalty-free redistribution of the Library 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 Library. - -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. - - 12. If the distribution and/or use of the Library is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Library 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. - - 13. The Free Software Foundation may publish revised and/or new -versions of the Lesser 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 Library -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 Library does not specify a -license version number, you may choose any version ever published by -the Free Software Foundation. - - 14. If you wish to incorporate parts of the Library into other free -programs whose distribution conditions are incompatible with these, -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 - - 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE LIBRARY "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 -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. 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 LIBRARY 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 -LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Libraries - - If you develop a new library, and you want it to be of the greatest -possible use to the public, we recommend making it free software that -everyone can redistribute and change. You can do so by permitting -redistribution under these terms (or, alternatively, under the terms of the -ordinary General Public License). - - To apply these terms, attach the following notices to the library. It is -safest to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least the -"copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -Also add information on how to contact you by electronic and paper mail. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the library, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the - library `Frob' (a library for tweaking knobs) written by James Random Hacker. - - , 1 April 1990 - Ty Coon, President of Vice - -That's all there is to it! - - + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +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 and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, 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 library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete 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 distribute a copy of this License along with the +Library. + + 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 Library or any portion +of it, thus forming a work based on the Library, 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) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +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 Library, 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 Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you 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. + + If distribution of 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 satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be 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. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library 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. + + 9. 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 Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +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 with +this License. + + 11. 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 Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library 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 Library. + +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. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library 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. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser 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 Library +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 Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +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 + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "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 +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. 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 LIBRARY 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 +LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/sites/all/modules/contrib/dev/devel/krumo/README b/sites/all/modules/contrib/dev/devel/krumo/README old mode 100755 new mode 100644 index 9391409..99513ea --- a/sites/all/modules/contrib/dev/devel/krumo/README +++ b/sites/all/modules/contrib/dev/devel/krumo/README @@ -1,103 +1,103 @@ -============================================================================= - - Krumo - version 0.2.1a - -============================================================================= - -You probably got this package from... -http://www.sourceforge.net/projects/krumo/ - -If there is no licence agreement with this package please download -a version from the location above. You must read and accept that -licence to use this software. The file is titled simply LICENSE. - -OVERVIEW ------------------------------------------------------------------------------- -To put it simply, Krumo is a replacement for print_r() and var_dump(). By -definition Krumo is a debugging tool (for PHP5), which displays structured -information about any PHP variable. - -A lot of developers use print_r() and var_dump() in the means of debugging -tools. Although they were intended to present human readble information about a -variable, we can all agree that in general they are not. Krumo is an -alternative: it does the same job, but it presents the information beautified -using CSS and DHTML. - -EXAMPLES ------------------------------------------------------------------------------- -Here's a basic example, which will return a report on the array variable passed -as argument to it: - - krumo(array('a1'=> 'A1', 3, 'red')); - -You can dump simultaneously more then one variable - here's another example: - - krumo($_SERVER, $_REQUEST); - -You probably saw from the examples above that some of the nodes are expandable, -so if you want to inspect the nested information, click on them and they will -expand; if you do not need that information shown simply click again on it to -collapse it. Here's an example to test this: - - $x1->x2->x3->x4->x5->x6->x7->x8->x9 = 'X10'; - krumo($x1); - -The krumo() is the only standalone function from the package, and this is -because basic dumps about variables (like print_r() or var_dump()) are the most -common tasks such functionality is used for. The rest of the functionality can -be called using static calls to the Krumo class. Here are several more examples: - - // print a debug backgrace - krumo::backtrace(); - - // print all the included(or required) files - krumo::includes(); - - // print all the included functions - krumo::functions(); - - // print all the declared classes - krumo::classes(); - - // print all the defined constants - krumo::defines(); - - ... and so on, etc. - -A full PHPDocumenter API documentation exists both in this package and at the -project's website. - -INSTALL ------------------------------------------------------------------------------- -Read the INSTALL file. - -DOCUMENTATION ------------------------------------------------------------------------------- -As I said, a full PHPDocumenter API documentation can be found both in this -package and at the project's website. - -SKINS ------------------------------------------------------------------------------- -There are several skins pre-installed with this package, but if you wish you can -create skins of your own. The skins are simply CSS files that are prepended to -the result that Krumo prints. If you want to use images in your CSS (for -background, list-style, etc), you have to put "%URL%" in front of the image URL -in order hook it up to the skin folder and make the image web-accessible. - -Here's an example: - - ul.krumo-first {background: url(%url%bg.gif);} - -TODO ------------------------------------------------------------------------------- -You can find the list of stuff that is going to be added to this project in the -TODO file from this very package. - -CONTRIBUTION ------------------------------------------------------------------------------ -If you download and use and possibly even extend this tool, please let us know. -Any feedback, even bad, is always welcome and your suggestions are going to be -considered for our next release. Please use our SourceForge page for that: - - http://www.sourceforge.net/projects/krumo/ +============================================================================= + + Krumo + version 0.2.1a + +============================================================================= + +You probably got this package from... +http://www.sourceforge.net/projects/krumo/ + +If there is no licence agreement with this package please download +a version from the location above. You must read and accept that +licence to use this software. The file is titled simply LICENSE. + +OVERVIEW +------------------------------------------------------------------------------ +To put it simply, Krumo is a replacement for print_r() and var_dump(). By +definition Krumo is a debugging tool (for PHP5), which displays structured +information about any PHP variable. + +A lot of developers use print_r() and var_dump() in the means of debugging +tools. Although they were intended to present human readble information about a +variable, we can all agree that in general they are not. Krumo is an +alternative: it does the same job, but it presents the information beautified +using CSS and DHTML. + +EXAMPLES +------------------------------------------------------------------------------ +Here's a basic example, which will return a report on the array variable passed +as argument to it: + + krumo(array('a1'=> 'A1', 3, 'red')); + +You can dump simultaneously more then one variable - here's another example: + + krumo($_SERVER, $_REQUEST); + +You probably saw from the examples above that some of the nodes are expandable, +so if you want to inspect the nested information, click on them and they will +expand; if you do not need that information shown simply click again on it to +collapse it. Here's an example to test this: + + $x1->x2->x3->x4->x5->x6->x7->x8->x9 = 'X10'; + krumo($x1); + +The krumo() is the only standalone function from the package, and this is +because basic dumps about variables (like print_r() or var_dump()) are the most +common tasks such functionality is used for. The rest of the functionality can +be called using static calls to the Krumo class. Here are several more examples: + + // print a debug backgrace + krumo::backtrace(); + + // print all the included(or required) files + krumo::includes(); + + // print all the included functions + krumo::functions(); + + // print all the declared classes + krumo::classes(); + + // print all the defined constants + krumo::defines(); + + ... and so on, etc. + +A full PHPDocumenter API documentation exists both in this package and at the +project's website. + +INSTALL +------------------------------------------------------------------------------ +Read the INSTALL file. + +DOCUMENTATION +------------------------------------------------------------------------------ +As I said, a full PHPDocumenter API documentation can be found both in this +package and at the project's website. + +SKINS +------------------------------------------------------------------------------ +There are several skins pre-installed with this package, but if you wish you can +create skins of your own. The skins are simply CSS files that are prepended to +the result that Krumo prints. If you want to use images in your CSS (for +background, list-style, etc), you have to put "%URL%" in front of the image URL +in order hook it up to the skin folder and make the image web-accessible. + +Here's an example: + + ul.krumo-first {background: url(%url%bg.gif);} + +TODO +------------------------------------------------------------------------------ +You can find the list of stuff that is going to be added to this project in the +TODO file from this very package. + +CONTRIBUTION +----------------------------------------------------------------------------- +If you download and use and possibly even extend this tool, please let us know. +Any feedback, even bad, is always welcome and your suggestions are going to be +considered for our next release. Please use our SourceForge page for that: + + http://www.sourceforge.net/projects/krumo/ diff --git a/sites/all/modules/contrib/dev/devel/krumo/TODO b/sites/all/modules/contrib/dev/devel/krumo/TODO old mode 100755 new mode 100644 index d1de49f..8989f9a --- a/sites/all/modules/contrib/dev/devel/krumo/TODO +++ b/sites/all/modules/contrib/dev/devel/krumo/TODO @@ -1,24 +1,24 @@ -****************************************************************************** - - Krumo: TODO - -****************************************************************************** - -BUGS ----------------- - - watch the SourceForge.net Bug Tracker - -Features: PHP ----------------- - - Try to detect anonymous (lambda) functions - - Try to detect whether an array is indexed or associated - - Add var_export support for arrays and objects - - Add JSON support for arrays and objects - -Features: GUI ----------------- - - Nicer and friendlier skin(s) - - Add top-level links for collapsing and expanding the whole tree - - Add object & array -level links for collapsing and expanding all the - nested nodes - - Print all parent classes for the rendered objects \ No newline at end of file +****************************************************************************** + + Krumo: TODO + +****************************************************************************** + +BUGS +---------------- + - watch the SourceForge.net Bug Tracker + +Features: PHP +---------------- + - Try to detect anonymous (lambda) functions + - Try to detect whether an array is indexed or associated + - Add var_export support for arrays and objects + - Add JSON support for arrays and objects + +Features: GUI +---------------- + - Nicer and friendlier skin(s) + - Add top-level links for collapsing and expanding the whole tree + - Add object & array -level links for collapsing and expanding all the + nested nodes + - Print all parent classes for the rendered objects diff --git a/sites/all/modules/contrib/dev/devel/krumo/VERSION b/sites/all/modules/contrib/dev/devel/krumo/VERSION old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/class.krumo.php b/sites/all/modules/contrib/dev/devel/krumo/class.krumo.php old mode 100755 new mode 100644 index 70da0b9..8276f74 --- a/sites/all/modules/contrib/dev/devel/krumo/class.krumo.php +++ b/sites/all/modules/contrib/dev/devel/krumo/class.krumo.php @@ -604,7 +604,8 @@ This is a list of all the values from the $bee){ - if (is_object($bee)) { + // skip closures set as properties + if (is_object($bee) && !($bee instanceof Closure)) { unset($hive[$i]->$_recursion_marker); // DEVEL: changed 'else' to 'elseif' below } elseif (is_array($bee)) { @@ -654,6 +655,15 @@ This is a list of all the values from the - - - - -$_recursion_marker++) + ? (empty($bee->$_recursion_marker) ? ($bee->$_recursion_marker = 1) : $bee->$_recursion_marker++) : @($bee[$_recursion_marker]++); $_[0][] =& $bee; @@ -973,6 +966,29 @@ This is a list of all the values from the getProperties() as $property) { + $k = $property->getName(); + if ($k === $_recursion_marker || $property->isPublic()) { + continue; + } + + // add key indicators + if ($property->isProtected()) { + $k .= ':protected'; + } + elseif ($property->isPrivate()) { + $k .= ':private'; + } + + $property->setAccessible(TRUE); + $v = $property->getValue($data); + krumo::_dump($v, $k); + } + } + // keys ? // $keys = ($_is_object) @@ -1020,7 +1036,7 @@ This is a list of all the values from the - + (Recursion) @@ -1044,10 +1060,7 @@ This is a list of all the values from the
  • -
    0) {?> onClick="krumo.toggle(this);" - onMouseOver="krumo.over(this);" - onMouseOut="krumo.out(this);"> +
    @@ -1096,10 +1109,7 @@ This is a list of all the values from the
  • -
    0) {?> onClick="krumo.toggle(this);" - onMouseOver="krumo.over(this);" - onMouseOut="krumo.out(this);"> +
    @@ -1107,7 +1117,7 @@ This is a list of all the values from the
    -
  • @@ -1243,16 +1253,13 @@ This is a list of all the values from the KRUMO_TRUNCATE_LENGTH) { - $_ = substr($data, 0, KRUMO_TRUNCATE_LENGTH - 3) . '...'; + $_ = drupal_substr($data, 0, KRUMO_TRUNCATE_LENGTH - 3) . '...'; $_extra = true; } ?>
  • -
    onClick="krumo.toggle(this);" - onMouseOver="krumo.over(this);" - onMouseOut="krumo.out(this);"> +
    diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/Krumo/_class.krumo.php.html b/sites/all/modules/contrib/dev/devel/krumo/docs/Krumo/_class.krumo.php.html index 6b3fcf4..cf49569 100644 --- a/sites/all/modules/contrib/dev/devel/krumo/docs/Krumo/_class.krumo.php.html +++ b/sites/all/modules/contrib/dev/devel/krumo/docs/Krumo/_class.krumo.php.html @@ -1,267 +1,268 @@ - - - - - - Docs for page class.krumo.php - - - - - -
    -

    File/class.krumo.php

    - - -
    -
    Description
    - -
    - -

    Krumo: Structured information display solution

    -

    Krumo is a debugging tool (PHP5 only), which displays structured information about any PHP variable. It is a nice replacement for print_r() or var_dump() which are used by a lot of PHP developers.

    - - -
    -
    - - -
    -
    Classes
    - -
    - - - - - - - - - -
    ClassDescription
    -  class - krumo - - Krumo API -
    -
    -
    - - - -
    -
    Constants
    - -
    - -
    - -
    - - - DIR_SEP = DIRECTORY_SEPARATOR - (line 22) - -
    - - -

    backward compatibility: the DIR_SEP constant isn't used anymore

    - - -
    - -
    - -
    - - - KRUMO_DIR = dirname(__FILE__).DIRECTORY_SEPARATOR - (line 39) - -
    - - -

    Set the KRUMO_DIR constant up with the absolute path to Krumo files. If it is not defined, include_path will be used. Set KRUMO_DIR only if any other module or application has not already set it up.

    - - -
    - -
    - -
    - - - KRUMO_TRUNCATE_LENGTH = 50 - (line 48) - -
    - - -

    This constant sets the maximum strings of strings that will be shown as they are. Longer strings will be truncated with this length, and their `full form` will be shown in a child node.

    - - -
    - -
    - -
    - - - PATH_SEPARATOR = OS_WINDOWS?';':':' - (line 28) - -
    - - -

    backward compatibility: the PATH_SEPARATOR constant is availble since 4.3.0RC2

    - - -
    -
    -
    - - - -
    -
    Functions
    - -
    - -
    - -
    - - krumo (line 1295) -
    - - -

    Alias of krumo::dump()

    - -
    - void - - krumo - - ([mixed $data,... = ]) -
    - -
      -
    • - mixed - $data,...
    • -
    - - -
    -
    -
    - -

    - Documentation generated on Sun, 02 Dec 2007 09:43:24 +0200 by phpDocumentor 1.4.0a2 -

    -
    - \ No newline at end of file + + + + + + Docs for page class.krumo.php + + + + + +
    + +

    File/class.krumo.php

    + + +
    +
    Description
    + +
    + +

    Krumo: Structured information display solution

    +

    Krumo is a debugging tool (PHP5 only), which displays structured information about any PHP variable. It is a nice replacement for print_r() or var_dump() which are used by a lot of PHP developers.

    + + +
    +
    + + +
    +
    Classes
    + +
    + + + + + + + + + +
    ClassDescription
    +  class + krumo + + Krumo API +
    +
    +
    + + + +
    +
    Constants
    + +
    + +
    + +
    + + + DIR_SEP = DIRECTORY_SEPARATOR + (line 22) + +
    + + +

    backward compatibility: the DIR_SEP constant isn't used anymore

    + + +
    + +
    + +
    + + + KRUMO_DIR = dirname(__FILE__).DIRECTORY_SEPARATOR + (line 39) + +
    + + +

    Set the KRUMO_DIR constant up with the absolute path to Krumo files. If it is not defined, include_path will be used. Set KRUMO_DIR only if any other module or application has not already set it up.

    + + +
    + +
    + +
    + + + KRUMO_TRUNCATE_LENGTH = 50 + (line 48) + +
    + + +

    This constant sets the maximum strings of strings that will be shown as they are. Longer strings will be truncated with this length, and their `full form` will be shown in a child node.

    + + +
    + +
    + +
    + + + PATH_SEPARATOR = OS_WINDOWS?';':':' + (line 28) + +
    + + +

    backward compatibility: the PATH_SEPARATOR constant is availble since 4.3.0RC2

    + + +
    +
    +
    + + + +
    +
    Functions
    + +
    + +
    + +
    + + krumo (line 1295) +
    + + +

    Alias of krumo::dump()

    + +
    + void + + krumo + + ([mixed $data,... = ]) +
    + +
      +
    • + mixed + $data,...
    • +
    + + +
    +
    +
    + +

    + Documentation generated on Sun, 02 Dec 2007 09:43:24 +0200 by phpDocumentor 1.4.0a2 +

    +
    + diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/Krumo/krumo.html b/sites/all/modules/contrib/dev/devel/krumo/docs/Krumo/krumo.html old mode 100755 new mode 100644 index ce4d33b..39557ef --- a/sites/all/modules/contrib/dev/devel/krumo/docs/Krumo/krumo.html +++ b/sites/all/modules/contrib/dev/devel/krumo/docs/Krumo/krumo.html @@ -1,900 +1,901 @@ - - - - - - Docs For Class krumo - - - - - -
    -

     Class krumo

    - - -
    -
    Description
    - -
    - -

    Krumo API

    -

    This class stores the Krumo API for rendering and displaying the structured information it is reporting

    -

    - Located in /class.krumo.php (line 61) -

    - - -
    
    -	
    -			
    -
    - - - - - -
    -
    Method Summary
    - -
    -
    - -
    -  - static void - backtrace - () -
    - -
    -  - static void - classes - () -
    - -
    -  - static void - conf - () -
    - -
    -  - static void - cookie - () -
    - -
    -  - static void - defines - () -
    - -
    -  - static boolean - disable - () -
    - -
    -  - static void - dump - ( $data, mixed $data,...) -
    - -
    -  - static boolean - enable - () -
    - -
    -  - static void - env - () -
    - -
    -  - static void - extensions - () -
    - -
    -  - static void - functions - () -
    - -
    -  - static void - get - () -
    - -
    -  - static void - headers - () -
    - -
    -  - static void - includes - () -
    - -
    -  - static void - ini - (string $ini_file) -
    - -
    -  - static void - interfaces - () -
    - -
    -  - static void - path - () -
    - -
    -  - static void - phpini - () -
    - -
    -  - static void - post - () -
    - -
    -  - static void - request - () -
    - -
    -  - static void - server - () -
    - -
    -  - static void - session - () -
    - -
    -  - static string - version - () -
    -
    -
    -
    - - - -
    -
    Methods
    - -
    - - -
    - -
    - - static backtrace (line 82) -
    - - -

    Prints a debug backtrace

    -
      -
    • access: public
    • -
    - -
    - static void - - backtrace - - () -
    - - - -
    - -
    - -
    - - static classes (line 101) -
    - - -

    Prints a list of all currently declared classes.

    -
      -
    • access: public
    • -
    - -
    - static void - - classes - - () -
    - - - -
    - -
    - -
    - - static conf (line 297) -
    - - -

    Prints a list of all your configuration settings.

    -
      -
    • access: public
    • -
    - -
    - static void - - conf - - () -
    - - - -
    - -
    - -
    - - static cookie (line 441) -
    - - -

    Prints a list of all the values from the $_COOKIE array.

    -
      -
    • access: public
    • -
    - -
    - static void - - cookie - - () -
    - - - -
    - -
    - -
    - - static defines (line 197) -
    - - -

    Prints a list of all currently declared constants.

    -
      -
    • access: public
    • -
    - -
    - static void - - defines - - () -
    - - - -
    - -
    - -
    - - static disable (line 747) -
    - - -

    Disable Krumo

    -
      -
    • access: public
    • -
    - -
    - static boolean - - disable - - () -
    - - - -
    - -
    - -
    - - static dump (line 548) -
    - - -

    Dump information about a variable

    -
      -
    • access: public
    • -
    - -
    - static void - - dump - - ( $data, mixed $data,...) -
    - -
      -
    • - mixed - $data,...
    • -
    • - - $data
    • -
    - - -
    - -
    - -
    - - static enable (line 736) -
    - - -

    Enable Krumo

    -
      -
    • access: public
    • -
    - -
    - static boolean - - enable - - () -
    - - - -
    - -
    - -
    - - static env (line 465) -
    - - -

    Prints a list of all the values from the $_ENV array.

    -
      -
    • access: public
    • -
    - -
    - static void - - env - - () -
    - - - -
    - -
    - -
    - - static extensions (line 221) -
    - - -

    Prints a list of all currently loaded PHP extensions.

    -
      -
    • access: public
    • -
    - -
    - static void - - extensions - - () -
    - - - -
    - -
    - -
    - - static functions (line 173) -
    - - -

    Prints a list of all currently declared functions.

    -
      -
    • access: public
    • -
    - -
    - static void - - functions - - () -
    - - - -
    - -
    - -
    - - static get (line 369) -
    - - -

    Prints a list of all the values from the $_GET array.

    -
      -
    • access: public
    • -
    - -
    - static void - - get - - () -
    - - - -
    - -
    - -
    - - static headers (line 245) -
    - - -

    Prints a list of all HTTP request headers.

    -
      -
    • access: public
    • -
    - -
    - static void - - headers - - () -
    - - - -
    - -
    - -
    - - static includes (line 149) -
    - - -

    Prints a list of all currently included (or required) files.

    -
      -
    • access: public
    • -
    - -
    - static void - - includes - - () -
    - - - -
    - -
    - -
    - - static ini (line 515) -
    - - -

    Prints a list of all the values from an INI file.

    -
      -
    • access: public
    • -
    - -
    - static void - - ini - - (string $ini_file) -
    - -
      -
    • - string - $ini_file
    • -
    - - -
    - -
    - -
    - - static interfaces (line 125) -
    - - -

    Prints a list of all currently declared interfaces (PHP5 only).

    -
      -
    • access: public
    • -
    - -
    - static void - - interfaces - - () -
    - - - -
    - -
    - -
    - - static path (line 321) -
    - - -

    Prints a list of the specified directories under your include_path option.

    -
      -
    • access: public
    • -
    - -
    - static void - - path - - () -
    - - - -
    - -
    - -
    - - static phpini (line 269) -
    - - -

    Prints a list of the configuration settings read from php.ini

    -
      -
    • access: public
    • -
    - -
    - static void - - phpini - - () -
    - - - -
    - -
    - -
    - - static post (line 393) -
    - - -

    Prints a list of all the values from the $_POST array.

    -
      -
    • access: public
    • -
    - -
    - static void - - post - - () -
    - - - -
    - -
    - -
    - - static request (line 345) -
    - - -

    Prints a list of all the values from the $_REQUEST array.

    -
      -
    • access: public
    • -
    - -
    - static void - - request - - () -
    - - - -
    - -
    - -
    - - static server (line 417) -
    - - -

    Prints a list of all the values from the $_SERVER array.

    -
      -
    • access: public
    • -
    - -
    - static void - - server - - () -
    - - - -
    - -
    - -
    - - static session (line 489) -
    - - -

    Prints a list of all the values from the $_SESSION array.

    -
      -
    • access: public
    • -
    - -
    - static void - - session - - () -
    - - - -
    - -
    - -
    - - static version (line 70) -
    - - -

    Return Krumo version

    -
      -
    • access: public
    • -
    - -
    - static string - - version - - () -
    - - - -
    - -
    -
    - - -

    - Documentation generated on Sun, 02 Dec 2007 09:43:24 +0200 by phpDocumentor 1.4.0a2 -

    -
    - \ No newline at end of file + + + + + + Docs For Class krumo + + + + + +
    + +

     Class krumo

    + + +
    +
    Description
    + +
    + +

    Krumo API

    +

    This class stores the Krumo API for rendering and displaying the structured information it is reporting

    +

    + Located in /class.krumo.php (line 61) +

    + + +
    
    +
    +    
    +
    + + + + + +
    +
    Method Summary
    + +
    +
    + +
    +  + static void + backtrace + () +
    + +
    +  + static void + classes + () +
    + +
    +  + static void + conf + () +
    + +
    +  + static void + cookie + () +
    + +
    +  + static void + defines + () +
    + +
    +  + static boolean + disable + () +
    + +
    +  + static void + dump + ( $data, mixed $data,...) +
    + +
    +  + static boolean + enable + () +
    + +
    +  + static void + env + () +
    + +
    +  + static void + extensions + () +
    + +
    +  + static void + functions + () +
    + +
    +  + static void + get + () +
    + +
    +  + static void + headers + () +
    + +
    +  + static void + includes + () +
    + +
    +  + static void + ini + (string $ini_file) +
    + +
    +  + static void + interfaces + () +
    + +
    +  + static void + path + () +
    + +
    +  + static void + phpini + () +
    + +
    +  + static void + post + () +
    + +
    +  + static void + request + () +
    + +
    +  + static void + server + () +
    + +
    +  + static void + session + () +
    + +
    +  + static string + version + () +
    +
    +
    +
    + + + +
    +
    Methods
    + +
    + + +
    + +
    + + static backtrace (line 82) +
    + + +

    Prints a debug backtrace

    +
      +
    • access: public
    • +
    + +
    + static void + + backtrace + + () +
    + + + +
    + +
    + +
    + + static classes (line 101) +
    + + +

    Prints a list of all currently declared classes.

    +
      +
    • access: public
    • +
    + +
    + static void + + classes + + () +
    + + + +
    + +
    + +
    + + static conf (line 297) +
    + + +

    Prints a list of all your configuration settings.

    +
      +
    • access: public
    • +
    + +
    + static void + + conf + + () +
    + + + +
    + +
    + +
    + + static cookie (line 441) +
    + + +

    Prints a list of all the values from the $_COOKIE array.

    +
      +
    • access: public
    • +
    + +
    + static void + + cookie + + () +
    + + + +
    + +
    + +
    + + static defines (line 197) +
    + + +

    Prints a list of all currently declared constants.

    +
      +
    • access: public
    • +
    + +
    + static void + + defines + + () +
    + + + +
    + +
    + +
    + + static disable (line 747) +
    + + +

    Disable Krumo

    +
      +
    • access: public
    • +
    + +
    + static boolean + + disable + + () +
    + + + +
    + +
    + +
    + + static dump (line 548) +
    + + +

    Dump information about a variable

    +
      +
    • access: public
    • +
    + +
    + static void + + dump + + ( $data, mixed $data,...) +
    + +
      +
    • + mixed + $data,...
    • +
    • + + $data
    • +
    + + +
    + +
    + +
    + + static enable (line 736) +
    + + +

    Enable Krumo

    +
      +
    • access: public
    • +
    + +
    + static boolean + + enable + + () +
    + + + +
    + +
    + +
    + + static env (line 465) +
    + + +

    Prints a list of all the values from the $_ENV array.

    +
      +
    • access: public
    • +
    + +
    + static void + + env + + () +
    + + + +
    + +
    + +
    + + static extensions (line 221) +
    + + +

    Prints a list of all currently loaded PHP extensions.

    +
      +
    • access: public
    • +
    + +
    + static void + + extensions + + () +
    + + + +
    + +
    + +
    + + static functions (line 173) +
    + + +

    Prints a list of all currently declared functions.

    +
      +
    • access: public
    • +
    + +
    + static void + + functions + + () +
    + + + +
    + +
    + +
    + + static get (line 369) +
    + + +

    Prints a list of all the values from the $_GET array.

    +
      +
    • access: public
    • +
    + +
    + static void + + get + + () +
    + + + +
    + +
    + +
    + + static headers (line 245) +
    + + +

    Prints a list of all HTTP request headers.

    +
      +
    • access: public
    • +
    + +
    + static void + + headers + + () +
    + + + +
    + +
    + +
    + + static includes (line 149) +
    + + +

    Prints a list of all currently included (or required) files.

    +
      +
    • access: public
    • +
    + +
    + static void + + includes + + () +
    + + + +
    + +
    + +
    + + static ini (line 515) +
    + + +

    Prints a list of all the values from an INI file.

    +
      +
    • access: public
    • +
    + +
    + static void + + ini + + (string $ini_file) +
    + +
      +
    • + string + $ini_file
    • +
    + + +
    + +
    + +
    + + static interfaces (line 125) +
    + + +

    Prints a list of all currently declared interfaces (PHP5 only).

    +
      +
    • access: public
    • +
    + +
    + static void + + interfaces + + () +
    + + + +
    + +
    + +
    + + static path (line 321) +
    + + +

    Prints a list of the specified directories under your include_path option.

    +
      +
    • access: public
    • +
    + +
    + static void + + path + + () +
    + + + +
    + +
    + +
    + + static phpini (line 269) +
    + + +

    Prints a list of the configuration settings read from php.ini

    +
      +
    • access: public
    • +
    + +
    + static void + + phpini + + () +
    + + + +
    + +
    + +
    + + static post (line 393) +
    + + +

    Prints a list of all the values from the $_POST array.

    +
      +
    • access: public
    • +
    + +
    + static void + + post + + () +
    + + + +
    + +
    + +
    + + static request (line 345) +
    + + +

    Prints a list of all the values from the $_REQUEST array.

    +
      +
    • access: public
    • +
    + +
    + static void + + request + + () +
    + + + +
    + +
    + +
    + + static server (line 417) +
    + + +

    Prints a list of all the values from the $_SERVER array.

    +
      +
    • access: public
    • +
    + +
    + static void + + server + + () +
    + + + +
    + +
    + +
    + + static session (line 489) +
    + + +

    Prints a list of all the values from the $_SESSION array.

    +
      +
    • access: public
    • +
    + +
    + static void + + session + + () +
    + + + +
    + +
    + +
    + + static version (line 70) +
    + + +

    Return Krumo version

    +
      +
    • access: public
    • +
    + +
    + static string + + version + + () +
    + + + +
    + +
    +
    + + +

    + Documentation generated on Sun, 02 Dec 2007 09:43:24 +0200 by phpDocumentor 1.4.0a2 +

    +
    + diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/blank.html b/sites/all/modules/contrib/dev/devel/krumo/docs/blank.html old mode 100755 new mode 100644 index 98c9636..cddfbe5 --- a/sites/all/modules/contrib/dev/devel/krumo/docs/blank.html +++ b/sites/all/modules/contrib/dev/devel/krumo/docs/blank.html @@ -1,13 +1,13 @@ - - - Krumo - - - - -

    Krumo

    -Welcome to Krumo!
    -
    -This documentation was generated by phpDocumentor v1.4.0a2
    - - \ No newline at end of file + + + Krumo + + + + +

    Krumo

    +Welcome to Krumo!
    +
    +This documentation was generated by phpDocumentor v1.4.0a2
    + + diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/classtrees_Krumo.html b/sites/all/modules/contrib/dev/devel/krumo/docs/classtrees_Krumo.html old mode 100755 new mode 100644 index 8123084..2c927b1 --- a/sites/all/modules/contrib/dev/devel/krumo/docs/classtrees_Krumo.html +++ b/sites/all/modules/contrib/dev/devel/krumo/docs/classtrees_Krumo.html @@ -1,23 +1,24 @@ - - - - - - - - - - - -

    - -

    -

    Root class krumo

    + + + + + + + + + + + + +

    + +

    +

    Root class krumo

    - -

    - Documentation generated on Sun, 02 Dec 2007 09:43:24 +0200 by phpDocumentor 1.4.0a2 -

    - - \ No newline at end of file + +

    + Documentation generated on Sun, 02 Dec 2007 09:43:24 +0200 by phpDocumentor 1.4.0a2 +

    + + diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/elementindex.html b/sites/all/modules/contrib/dev/devel/krumo/docs/elementindex.html old mode 100755 new mode 100644 index bc89b7a..a2a43cf --- a/sites/all/modules/contrib/dev/devel/krumo/docs/elementindex.html +++ b/sites/all/modules/contrib/dev/devel/krumo/docs/elementindex.html @@ -1,392 +1,394 @@ - - - - - - - - - - -

    Full index

    -

    Package indexes

    - -
    -
    - b - c - d - e - f - g - h - i - k - p - r - s - v -
    - - -
    -
    b
    - -
    -
    -
    -
    - Method - backtrace -
    -
    -
    krumo::backtrace() in class.krumo.php
    -
    Prints a debug backtrace
    -
    -
    - -
    -
    c
    - -
    -
    -
    -
    - Page - class.krumo.php -
    -
    -
    class.krumo.php in class.krumo.php
    -
    -
    - Method - classes -
    -
    -
    krumo::classes() in class.krumo.php
    -
    Prints a list of all currently declared classes.
    -
    -
    - Method - conf -
    -
    -
    krumo::conf() in class.krumo.php
    -
    Prints a list of all your configuration settings.
    -
    -
    - Method - cookie -
    -
    -
    krumo::cookie() in class.krumo.php
    -
    Prints a list of all the values from the $_COOKIE array.
    -
    -
    - -
    -
    d
    - -
    -
    -
    -
    - Method - defines -
    -
    -
    krumo::defines() in class.krumo.php
    -
    Prints a list of all currently declared constants.
    -
    -
    - Constant - DIR_SEP -
    -
    -
    DIR_SEP in class.krumo.php
    -
    backward compatibility: the DIR_SEP constant isn't used anymore
    -
    -
    - Method - disable -
    -
    -
    krumo::disable() in class.krumo.php
    -
    Disable Krumo
    -
    -
    - Method - dump -
    -
    -
    krumo::dump() in class.krumo.php
    -
    Dump information about a variable
    -
    -
    - -
    -
    e
    - -
    -
    -
    -
    - Method - enable -
    -
    -
    krumo::enable() in class.krumo.php
    -
    Enable Krumo
    -
    -
    - Method - env -
    -
    -
    krumo::env() in class.krumo.php
    -
    Prints a list of all the values from the $_ENV array.
    -
    -
    - Method - extensions -
    -
    -
    krumo::extensions() in class.krumo.php
    -
    Prints a list of all currently loaded PHP extensions.
    -
    -
    - -
    -
    f
    - -
    -
    -
    -
    - Method - functions -
    -
    -
    krumo::functions() in class.krumo.php
    -
    Prints a list of all currently declared functions.
    -
    -
    - -
    -
    g
    - -
    -
    -
    -
    - Method - get -
    -
    -
    krumo::get() in class.krumo.php
    -
    Prints a list of all the values from the $_GET array.
    -
    -
    - -
    -
    h
    - -
    -
    -
    -
    - Method - headers -
    -
    -
    krumo::headers() in class.krumo.php
    -
    Prints a list of all HTTP request headers.
    -
    -
    - -
    -
    i
    - -
    -
    -
    -
    - Method - includes -
    -
    -
    krumo::includes() in class.krumo.php
    -
    Prints a list of all currently included (or required) files.
    -
    -
    - Method - ini -
    -
    -
    krumo::ini() in class.krumo.php
    -
    Prints a list of all the values from an INI file.
    -
    -
    - Method - interfaces -
    -
    -
    krumo::interfaces() in class.krumo.php
    -
    Prints a list of all currently declared interfaces (PHP5 only).
    -
    -
    - -
    -
    k
    - -
    -
    -
    -
    - Function - krumo -
    -
    -
    krumo() in class.krumo.php
    -
    Alias of krumo::dump()
    -
    -
    - Class - krumo -
    -
    -
    krumo in class.krumo.php
    -
    Krumo API
    -
    -
    - Constant - KRUMO_DIR -
    -
    -
    KRUMO_DIR in class.krumo.php
    -
    Set the KRUMO_DIR constant up with the absolute path to Krumo files. If it is not defined, include_path will be used. Set KRUMO_DIR only if any other module or application has not already set it up.
    -
    -
    - Constant - KRUMO_TRUNCATE_LENGTH -
    -
    -
    KRUMO_TRUNCATE_LENGTH in class.krumo.php
    -
    This constant sets the maximum strings of strings that will be shown as they are. Longer strings will be truncated with this length, and their `full form` will be shown in a child node.
    -
    -
    - -
    -
    p
    - -
    -
    -
    -
    - Method - path -
    -
    -
    krumo::path() in class.krumo.php
    -
    Prints a list of the specified directories under your include_path option.
    -
    -
    - Constant - PATH_SEPARATOR -
    -
    -
    PATH_SEPARATOR in class.krumo.php
    -
    backward compatibility: the PATH_SEPARATOR constant is availble since 4.3.0RC2
    -
    -
    - Method - phpini -
    -
    -
    krumo::phpini() in class.krumo.php
    -
    Prints a list of the configuration settings read from php.ini
    -
    -
    - Method - post -
    -
    -
    krumo::post() in class.krumo.php
    -
    Prints a list of all the values from the $_POST array.
    -
    -
    - -
    -
    r
    - -
    -
    -
    -
    - Method - request -
    -
    -
    krumo::request() in class.krumo.php
    -
    Prints a list of all the values from the $_REQUEST array.
    -
    -
    - -
    -
    s
    - -
    -
    -
    -
    - Method - server -
    -
    -
    krumo::server() in class.krumo.php
    -
    Prints a list of all the values from the $_SERVER array.
    -
    -
    - Method - session -
    -
    -
    krumo::session() in class.krumo.php
    -
    Prints a list of all the values from the $_SESSION array.
    -
    -
    - -
    -
    v
    - -
    -
    -
    -
    - Method - version -
    -
    -
    krumo::version() in class.krumo.php
    -
    Return Krumo version
    -
    -
    - -
    - b - c - d - e - f - g - h - i - k - p - r - s - v -
    - \ No newline at end of file + + + + + + + + + + + +

    Full index

    +

    Package indexes

    + +
    +
    + b + c + d + e + f + g + h + i + k + p + r + s + v +
    + + +
    +
    b
    + +
    +
    +
    +
    + Method + backtrace +
    +
    +
    krumo::backtrace() in class.krumo.php
    +
    Prints a debug backtrace
    +
    +
    + +
    +
    c
    + +
    +
    +
    +
    + Page + class.krumo.php +
    +
    +
    class.krumo.php in class.krumo.php
    +
    +
    + Method + classes +
    +
    +
    krumo::classes() in class.krumo.php
    +
    Prints a list of all currently declared classes.
    +
    +
    + Method + conf +
    +
    +
    krumo::conf() in class.krumo.php
    +
    Prints a list of all your configuration settings.
    +
    +
    + Method + cookie +
    +
    +
    krumo::cookie() in class.krumo.php
    +
    Prints a list of all the values from the $_COOKIE array.
    +
    +
    + +
    +
    d
    + +
    +
    +
    +
    + Method + defines +
    +
    +
    krumo::defines() in class.krumo.php
    +
    Prints a list of all currently declared constants.
    +
    +
    + Constant + DIR_SEP +
    +
    +
    DIR_SEP in class.krumo.php
    +
    backward compatibility: the DIR_SEP constant isn't used anymore
    +
    +
    + Method + disable +
    +
    +
    krumo::disable() in class.krumo.php
    +
    Disable Krumo
    +
    +
    + Method + dump +
    +
    +
    krumo::dump() in class.krumo.php
    +
    Dump information about a variable
    +
    +
    + +
    +
    e
    + +
    +
    +
    +
    + Method + enable +
    +
    +
    krumo::enable() in class.krumo.php
    +
    Enable Krumo
    +
    +
    + Method + env +
    +
    +
    krumo::env() in class.krumo.php
    +
    Prints a list of all the values from the $_ENV array.
    +
    +
    + Method + extensions +
    +
    +
    krumo::extensions() in class.krumo.php
    +
    Prints a list of all currently loaded PHP extensions.
    +
    +
    + +
    +
    f
    + +
    +
    +
    +
    + Method + functions +
    +
    +
    krumo::functions() in class.krumo.php
    +
    Prints a list of all currently declared functions.
    +
    +
    + +
    +
    g
    + +
    +
    +
    +
    + Method + get +
    +
    +
    krumo::get() in class.krumo.php
    +
    Prints a list of all the values from the $_GET array.
    +
    +
    + +
    +
    h
    + +
    +
    +
    +
    + Method + headers +
    +
    +
    krumo::headers() in class.krumo.php
    +
    Prints a list of all HTTP request headers.
    +
    +
    + +
    +
    i
    + +
    +
    +
    +
    + Method + includes +
    +
    +
    krumo::includes() in class.krumo.php
    +
    Prints a list of all currently included (or required) files.
    +
    +
    + Method + ini +
    +
    +
    krumo::ini() in class.krumo.php
    +
    Prints a list of all the values from an INI file.
    +
    +
    + Method + interfaces +
    +
    +
    krumo::interfaces() in class.krumo.php
    +
    Prints a list of all currently declared interfaces (PHP5 only).
    +
    +
    + +
    +
    k
    + +
    +
    +
    +
    + Function + krumo +
    +
    +
    krumo() in class.krumo.php
    +
    Alias of krumo::dump()
    +
    +
    + Class + krumo +
    +
    +
    krumo in class.krumo.php
    +
    Krumo API
    +
    +
    + Constant + KRUMO_DIR +
    +
    +
    KRUMO_DIR in class.krumo.php
    +
    Set the KRUMO_DIR constant up with the absolute path to Krumo files. If it is not defined, include_path will be used. Set KRUMO_DIR only if any other module or application has not already set it up.
    +
    +
    + Constant + KRUMO_TRUNCATE_LENGTH +
    +
    +
    KRUMO_TRUNCATE_LENGTH in class.krumo.php
    +
    This constant sets the maximum strings of strings that will be shown as they are. Longer strings will be truncated with this length, and their `full form` will be shown in a child node.
    +
    +
    + +
    +
    p
    + +
    +
    +
    +
    + Method + path +
    +
    +
    krumo::path() in class.krumo.php
    +
    Prints a list of the specified directories under your include_path option.
    +
    +
    + Constant + PATH_SEPARATOR +
    +
    +
    PATH_SEPARATOR in class.krumo.php
    +
    backward compatibility: the PATH_SEPARATOR constant is availble since 4.3.0RC2
    +
    +
    + Method + phpini +
    +
    +
    krumo::phpini() in class.krumo.php
    +
    Prints a list of the configuration settings read from php.ini
    +
    +
    + Method + post +
    +
    +
    krumo::post() in class.krumo.php
    +
    Prints a list of all the values from the $_POST array.
    +
    +
    + +
    +
    r
    + +
    +
    +
    +
    + Method + request +
    +
    +
    krumo::request() in class.krumo.php
    +
    Prints a list of all the values from the $_REQUEST array.
    +
    +
    + +
    +
    s
    + +
    +
    +
    +
    + Method + server +
    +
    +
    krumo::server() in class.krumo.php
    +
    Prints a list of all the values from the $_SERVER array.
    +
    +
    + Method + session +
    +
    +
    krumo::session() in class.krumo.php
    +
    Prints a list of all the values from the $_SESSION array.
    +
    +
    + +
    +
    v
    + +
    +
    +
    +
    + Method + version +
    +
    +
    krumo::version() in class.krumo.php
    +
    Return Krumo version
    +
    +
    + +
    + b + c + d + e + f + g + h + i + k + p + r + s + v +
    + + diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/elementindex_Krumo.html b/sites/all/modules/contrib/dev/devel/krumo/docs/elementindex_Krumo.html old mode 100755 new mode 100644 index 39e7477..3cd941f --- a/sites/all/modules/contrib/dev/devel/krumo/docs/elementindex_Krumo.html +++ b/sites/all/modules/contrib/dev/devel/krumo/docs/elementindex_Krumo.html @@ -1,389 +1,391 @@ - - - - - - - - - - -

    [Krumo] element index

    -All elements -
    -
    - b - c - d - e - f - g - h - i - k - p - r - s - v -
    - - -
    -
    b
    - -
    -
    -
    -
    - Method - backtrace -
    -
    -
    krumo::backtrace() in class.krumo.php
    -
    Prints a debug backtrace
    -
    -
    - -
    -
    c
    - -
    -
    -
    -
    - Page - class.krumo.php -
    -
    -
    class.krumo.php in class.krumo.php
    -
    -
    - Method - classes -
    -
    -
    krumo::classes() in class.krumo.php
    -
    Prints a list of all currently declared classes.
    -
    -
    - Method - conf -
    -
    -
    krumo::conf() in class.krumo.php
    -
    Prints a list of all your configuration settings.
    -
    -
    - Method - cookie -
    -
    -
    krumo::cookie() in class.krumo.php
    -
    Prints a list of all the values from the $_COOKIE array.
    -
    -
    - -
    -
    d
    - -
    -
    -
    -
    - Method - defines -
    -
    -
    krumo::defines() in class.krumo.php
    -
    Prints a list of all currently declared constants.
    -
    -
    - Constant - DIR_SEP -
    -
    -
    DIR_SEP in class.krumo.php
    -
    backward compatibility: the DIR_SEP constant isn't used anymore
    -
    -
    - Method - disable -
    -
    -
    krumo::disable() in class.krumo.php
    -
    Disable Krumo
    -
    -
    - Method - dump -
    -
    -
    krumo::dump() in class.krumo.php
    -
    Dump information about a variable
    -
    -
    - -
    -
    e
    - -
    -
    -
    -
    - Method - enable -
    -
    -
    krumo::enable() in class.krumo.php
    -
    Enable Krumo
    -
    -
    - Method - env -
    -
    -
    krumo::env() in class.krumo.php
    -
    Prints a list of all the values from the $_ENV array.
    -
    -
    - Method - extensions -
    -
    -
    krumo::extensions() in class.krumo.php
    -
    Prints a list of all currently loaded PHP extensions.
    -
    -
    - -
    -
    f
    - -
    -
    -
    -
    - Method - functions -
    -
    -
    krumo::functions() in class.krumo.php
    -
    Prints a list of all currently declared functions.
    -
    -
    - -
    -
    g
    - -
    -
    -
    -
    - Method - get -
    -
    -
    krumo::get() in class.krumo.php
    -
    Prints a list of all the values from the $_GET array.
    -
    -
    - -
    -
    h
    - -
    -
    -
    -
    - Method - headers -
    -
    -
    krumo::headers() in class.krumo.php
    -
    Prints a list of all HTTP request headers.
    -
    -
    - -
    -
    i
    - -
    -
    -
    -
    - Method - includes -
    -
    -
    krumo::includes() in class.krumo.php
    -
    Prints a list of all currently included (or required) files.
    -
    -
    - Method - ini -
    -
    -
    krumo::ini() in class.krumo.php
    -
    Prints a list of all the values from an INI file.
    -
    -
    - Method - interfaces -
    -
    -
    krumo::interfaces() in class.krumo.php
    -
    Prints a list of all currently declared interfaces (PHP5 only).
    -
    -
    - -
    -
    k
    - -
    -
    -
    -
    - Function - krumo -
    -
    -
    krumo() in class.krumo.php
    -
    Alias of krumo::dump()
    -
    -
    - Class - krumo -
    -
    -
    krumo in class.krumo.php
    -
    Krumo API
    -
    -
    - Constant - KRUMO_DIR -
    -
    -
    KRUMO_DIR in class.krumo.php
    -
    Set the KRUMO_DIR constant up with the absolute path to Krumo files. If it is not defined, include_path will be used. Set KRUMO_DIR only if any other module or application has not already set it up.
    -
    -
    - Constant - KRUMO_TRUNCATE_LENGTH -
    -
    -
    KRUMO_TRUNCATE_LENGTH in class.krumo.php
    -
    This constant sets the maximum strings of strings that will be shown as they are. Longer strings will be truncated with this length, and their `full form` will be shown in a child node.
    -
    -
    - -
    -
    p
    - -
    -
    -
    -
    - Method - path -
    -
    -
    krumo::path() in class.krumo.php
    -
    Prints a list of the specified directories under your include_path option.
    -
    -
    - Constant - PATH_SEPARATOR -
    -
    -
    PATH_SEPARATOR in class.krumo.php
    -
    backward compatibility: the PATH_SEPARATOR constant is availble since 4.3.0RC2
    -
    -
    - Method - phpini -
    -
    -
    krumo::phpini() in class.krumo.php
    -
    Prints a list of the configuration settings read from php.ini
    -
    -
    - Method - post -
    -
    -
    krumo::post() in class.krumo.php
    -
    Prints a list of all the values from the $_POST array.
    -
    -
    - -
    -
    r
    - -
    -
    -
    -
    - Method - request -
    -
    -
    krumo::request() in class.krumo.php
    -
    Prints a list of all the values from the $_REQUEST array.
    -
    -
    - -
    -
    s
    - -
    -
    -
    -
    - Method - server -
    -
    -
    krumo::server() in class.krumo.php
    -
    Prints a list of all the values from the $_SERVER array.
    -
    -
    - Method - session -
    -
    -
    krumo::session() in class.krumo.php
    -
    Prints a list of all the values from the $_SESSION array.
    -
    -
    - -
    -
    v
    - -
    -
    -
    -
    - Method - version -
    -
    -
    krumo::version() in class.krumo.php
    -
    Return Krumo version
    -
    -
    - -
    - b - c - d - e - f - g - h - i - k - p - r - s - v -
    - \ No newline at end of file + + + + + + + + + + + +

    [Krumo] element index

    +All elements +
    +
    + b + c + d + e + f + g + h + i + k + p + r + s + v +
    + + +
    +
    b
    + +
    +
    +
    +
    + Method + backtrace +
    +
    +
    krumo::backtrace() in class.krumo.php
    +
    Prints a debug backtrace
    +
    +
    + +
    +
    c
    + +
    +
    +
    +
    + Page + class.krumo.php +
    +
    +
    class.krumo.php in class.krumo.php
    +
    +
    + Method + classes +
    +
    +
    krumo::classes() in class.krumo.php
    +
    Prints a list of all currently declared classes.
    +
    +
    + Method + conf +
    +
    +
    krumo::conf() in class.krumo.php
    +
    Prints a list of all your configuration settings.
    +
    +
    + Method + cookie +
    +
    +
    krumo::cookie() in class.krumo.php
    +
    Prints a list of all the values from the $_COOKIE array.
    +
    +
    + +
    +
    d
    + +
    +
    +
    +
    + Method + defines +
    +
    +
    krumo::defines() in class.krumo.php
    +
    Prints a list of all currently declared constants.
    +
    +
    + Constant + DIR_SEP +
    +
    +
    DIR_SEP in class.krumo.php
    +
    backward compatibility: the DIR_SEP constant isn't used anymore
    +
    +
    + Method + disable +
    +
    +
    krumo::disable() in class.krumo.php
    +
    Disable Krumo
    +
    +
    + Method + dump +
    +
    +
    krumo::dump() in class.krumo.php
    +
    Dump information about a variable
    +
    +
    + +
    +
    e
    + +
    +
    +
    +
    + Method + enable +
    +
    +
    krumo::enable() in class.krumo.php
    +
    Enable Krumo
    +
    +
    + Method + env +
    +
    +
    krumo::env() in class.krumo.php
    +
    Prints a list of all the values from the $_ENV array.
    +
    +
    + Method + extensions +
    +
    +
    krumo::extensions() in class.krumo.php
    +
    Prints a list of all currently loaded PHP extensions.
    +
    +
    + +
    +
    f
    + +
    +
    +
    +
    + Method + functions +
    +
    +
    krumo::functions() in class.krumo.php
    +
    Prints a list of all currently declared functions.
    +
    +
    + +
    +
    g
    + +
    +
    +
    +
    + Method + get +
    +
    +
    krumo::get() in class.krumo.php
    +
    Prints a list of all the values from the $_GET array.
    +
    +
    + +
    +
    h
    + +
    +
    +
    +
    + Method + headers +
    +
    +
    krumo::headers() in class.krumo.php
    +
    Prints a list of all HTTP request headers.
    +
    +
    + +
    +
    i
    + +
    +
    +
    +
    + Method + includes +
    +
    +
    krumo::includes() in class.krumo.php
    +
    Prints a list of all currently included (or required) files.
    +
    +
    + Method + ini +
    +
    +
    krumo::ini() in class.krumo.php
    +
    Prints a list of all the values from an INI file.
    +
    +
    + Method + interfaces +
    +
    +
    krumo::interfaces() in class.krumo.php
    +
    Prints a list of all currently declared interfaces (PHP5 only).
    +
    +
    + +
    +
    k
    + +
    +
    +
    +
    + Function + krumo +
    +
    +
    krumo() in class.krumo.php
    +
    Alias of krumo::dump()
    +
    +
    + Class + krumo +
    +
    +
    krumo in class.krumo.php
    +
    Krumo API
    +
    +
    + Constant + KRUMO_DIR +
    +
    +
    KRUMO_DIR in class.krumo.php
    +
    Set the KRUMO_DIR constant up with the absolute path to Krumo files. If it is not defined, include_path will be used. Set KRUMO_DIR only if any other module or application has not already set it up.
    +
    +
    + Constant + KRUMO_TRUNCATE_LENGTH +
    +
    +
    KRUMO_TRUNCATE_LENGTH in class.krumo.php
    +
    This constant sets the maximum strings of strings that will be shown as they are. Longer strings will be truncated with this length, and their `full form` will be shown in a child node.
    +
    +
    + +
    +
    p
    + +
    +
    +
    +
    + Method + path +
    +
    +
    krumo::path() in class.krumo.php
    +
    Prints a list of the specified directories under your include_path option.
    +
    +
    + Constant + PATH_SEPARATOR +
    +
    +
    PATH_SEPARATOR in class.krumo.php
    +
    backward compatibility: the PATH_SEPARATOR constant is availble since 4.3.0RC2
    +
    +
    + Method + phpini +
    +
    +
    krumo::phpini() in class.krumo.php
    +
    Prints a list of the configuration settings read from php.ini
    +
    +
    + Method + post +
    +
    +
    krumo::post() in class.krumo.php
    +
    Prints a list of all the values from the $_POST array.
    +
    +
    + +
    +
    r
    + +
    +
    +
    +
    + Method + request +
    +
    +
    krumo::request() in class.krumo.php
    +
    Prints a list of all the values from the $_REQUEST array.
    +
    +
    + +
    +
    s
    + +
    +
    +
    +
    + Method + server +
    +
    +
    krumo::server() in class.krumo.php
    +
    Prints a list of all the values from the $_SERVER array.
    +
    +
    + Method + session +
    +
    +
    krumo::session() in class.krumo.php
    +
    Prints a list of all the values from the $_SESSION array.
    +
    +
    + +
    +
    v
    + +
    +
    +
    +
    + Method + version +
    +
    +
    krumo::version() in class.krumo.php
    +
    Return Krumo version
    +
    +
    + +
    + b + c + d + e + f + g + h + i + k + p + r + s + v +
    + + diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/errors.html b/sites/all/modules/contrib/dev/devel/krumo/docs/errors.html old mode 100755 new mode 100644 index cf21fd4..c53aced --- a/sites/all/modules/contrib/dev/devel/krumo/docs/errors.html +++ b/sites/all/modules/contrib/dev/devel/krumo/docs/errors.html @@ -1,15 +1,16 @@ - - - - - - phpDocumentor Parser Errors and Warnings - - - - Post-parsing
    -

    - Documentation generated on Sun, 02 Dec 2007 09:43:25 +0200 by phpDocumentor 1.4.0a2 -

    - - \ No newline at end of file + + + + + + phpDocumentor Parser Errors and Warnings + + + + + Post-parsing
    +

    + Documentation generated on Sun, 02 Dec 2007 09:43:25 +0200 by phpDocumentor 1.4.0a2 +

    + + diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/index.html b/sites/all/modules/contrib/dev/devel/krumo/docs/index.html old mode 100755 new mode 100644 index 8276a70..89518ce --- a/sites/all/modules/contrib/dev/devel/krumo/docs/index.html +++ b/sites/all/modules/contrib/dev/devel/krumo/docs/index.html @@ -1,24 +1,24 @@ - - - - - - Krumo - - - - - - - - - - - <H2>Frame Alert</H2> - <P>This document is designed to be viewed using the frames feature. - If you see this message, you are using a non-frame-capable web client.</P> - - - \ No newline at end of file + + + + + + Krumo + + + + + + + + + + + <H2>Frame Alert</H2> + <P>This document is designed to be viewed using the frames feature. + If you see this message, you are using a non-frame-capable web client.</P> + + + diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/li_Krumo.html b/sites/all/modules/contrib/dev/devel/krumo/docs/li_Krumo.html old mode 100755 new mode 100644 index f353e8d..d369270 --- a/sites/all/modules/contrib/dev/devel/krumo/docs/li_Krumo.html +++ b/sites/all/modules/contrib/dev/devel/krumo/docs/li_Krumo.html @@ -1,155 +1,156 @@ - - - - - - - - - - - - -

    Krumo

    -
    - -
    -

    - Generated by - phpDocumentor 1.4.0a2 -

    - - \ No newline at end of file + + + + + + + + + + + + + +

    Krumo

    +
    + +
    +

    + Generated by + phpDocumentor 1.4.0a2 +

    + + diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/banner.css b/sites/all/modules/contrib/dev/devel/krumo/docs/media/banner.css old mode 100755 new mode 100644 index 1b7fa8a..19a383c --- a/sites/all/modules/contrib/dev/devel/krumo/docs/media/banner.css +++ b/sites/all/modules/contrib/dev/devel/krumo/docs/media/banner.css @@ -1,32 +1,32 @@ -body -{ - background-color: #EEEEEE; - margin: 0px; - padding: 0px; -} - -/* Banner (top bar) classes */ - -.banner { } - -.banner-menu -{ - clear: both; - padding: .5em; - border-top: 2px solid #AAAAAA; -} - -.banner-title -{ - text-align: right; - font-size: 20pt; - font-weight: bold; - margin: .2em; -} - -.package-selector -{ - background-color: #DDDDDD; - border: 1px solid #AAAAAA; - color: #000090; -} +body +{ + background-color: #EEEEEE; + margin: 0px; + padding: 0px; +} + +/* Banner (top bar) classes */ + +.banner { } + +.banner-menu +{ + clear: both; + padding: .5em; + border-top: 2px solid #AAAAAA; +} + +.banner-title +{ + text-align: right; + font-size: 20pt; + font-weight: bold; + margin: .2em; +} + +.package-selector +{ + background-color: #DDDDDD; + border: 1px solid #AAAAAA; + color: #000090; +} diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/AbstractClass.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/AbstractClass.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/AbstractClass_logo.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/AbstractClass_logo.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/AbstractMethod.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/AbstractMethod.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/AbstractPrivateClass.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/AbstractPrivateClass.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/AbstractPrivateClass_logo.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/AbstractPrivateClass_logo.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/AbstractPrivateMethod.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/AbstractPrivateMethod.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/Class.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/Class.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/Class_logo.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/Class_logo.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/Constant.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/Constant.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/Constructor.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/Constructor.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/Destructor.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/Destructor.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/Function.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/Function.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/Global.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/Global.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/I.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/I.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/Index.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/Index.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/Interface.PNG b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/Interface.png similarity index 100% rename from sites/all/modules/contrib/dev/devel/krumo/docs/media/images/Interface.PNG rename to sites/all/modules/contrib/dev/devel/krumo/docs/media/images/Interface.png diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/L.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/L.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/Lminus.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/Lminus.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/Lplus.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/Lplus.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/Method.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/Method.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/Page.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/Page.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/Page_logo.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/Page_logo.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/PrivateClass.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/PrivateClass.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/PrivateClass_logo.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/PrivateClass_logo.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/PrivateMethod.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/PrivateMethod.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/PrivateVariable.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/PrivateVariable.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/T.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/T.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/Tminus.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/Tminus.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/Tplus.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/Tplus.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/Variable.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/Variable.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/blank.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/blank.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/class_folder.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/class_folder.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/empty.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/empty.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/file.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/file.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/folder.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/folder.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/function_folder.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/function_folder.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/minus.gif b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/minus.gif old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/next_button.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/next_button.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/next_button_disabled.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/next_button_disabled.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/package.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/package.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/package_folder.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/package_folder.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/plus.gif b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/plus.gif old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/previous_button.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/previous_button.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/previous_button_disabled.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/previous_button_disabled.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/private_class_logo.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/private_class_logo.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/tutorial.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/tutorial.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/tutorial_folder.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/tutorial_folder.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/up_button.png b/sites/all/modules/contrib/dev/devel/krumo/docs/media/images/up_button.png old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/lib/classTree.js b/sites/all/modules/contrib/dev/devel/krumo/docs/media/lib/classTree.js old mode 100755 new mode 100644 index 5989426..63a1b30 --- a/sites/all/modules/contrib/dev/devel/krumo/docs/media/lib/classTree.js +++ b/sites/all/modules/contrib/dev/devel/krumo/docs/media/lib/classTree.js @@ -1,454 +1,454 @@ -/*----------------------------------------\ -| Cross Browser Tree Widget 1.1 | -|-----------------------------------------| -| Created by Emil A. Eklund (eae@eae.net) | -| For WebFX (http://webfx.eae.net/) | -|-----------------------------------------| -| This script is provided as is without | -| any warranty whatsoever. It may be used | -| free of charge for non commerical sites | -| For commerical use contact the author | -| of this script for further details. | -|-----------------------------------------| -| Created 2000-12-11 | Updated 2001-09-06 | -\----------------------------------------*/ - -var webFXTreeConfig = { - rootIcon : 'media/images/empty.png', - openRootIcon : 'media/images/empty.png', - folderIcon : 'media/images/empty.png', - openFolderIcon : 'media/images/empty.png', - fileIcon : 'media/images/empty.png', - iIcon : 'media/images/I.png', - lIcon : 'media/images/L.png', - lMinusIcon : 'media/images/Lminus.png', - lPlusIcon : 'media/images/Lplus.png', - tIcon : 'media/images/T.png', - tMinusIcon : 'media/images/Tminus.png', - tPlusIcon : 'media/images/Tplus.png', - blankIcon : 'media/images/blank.png', - defaultText : 'Tree Item', - defaultAction : 'javascript:void(0);', - defaultTarget : 'right', - defaultBehavior : 'classic' -}; - -var webFXTreeHandler = { - idCounter : 0, - idPrefix : "webfx-tree-object-", - all : {}, - behavior : null, - selected : null, - getId : function() { return this.idPrefix + this.idCounter++; }, - toggle : function (oItem) { this.all[oItem.id.replace('-plus','')].toggle(); }, - select : function (oItem) { this.all[oItem.id.replace('-icon','')].select(); }, - focus : function (oItem) { this.all[oItem.id.replace('-anchor','')].focus(); }, - blur : function (oItem) { this.all[oItem.id.replace('-anchor','')].blur(); }, - keydown : function (oItem) { return this.all[oItem.id].keydown(window.event.keyCode); }, - cookies : new WebFXCookie() -}; - -/* - * WebFXCookie class - */ - -function WebFXCookie() { - if (document.cookie.length) { this.cookies = ' ' + document.cookie; } -} - -WebFXCookie.prototype.setCookie = function (key, value) { - document.cookie = key + "=" + escape(value); -} - -WebFXCookie.prototype.getCookie = function (key) { - if (this.cookies) { - var start = this.cookies.indexOf(' ' + key + '='); - if (start == -1) { return null; } - var end = this.cookies.indexOf(";", start); - if (end == -1) { end = this.cookies.length; } - end -= start; - var cookie = this.cookies.substr(start,end); - return unescape(cookie.substr(cookie.indexOf('=') + 1, cookie.length - cookie.indexOf('=') + 1)); - } - else { return null; } -} - -/* - * WebFXTreeAbstractNode class - */ - -function WebFXTreeAbstractNode(sText, sAction, sTarget) { - this.childNodes = []; - this.id = webFXTreeHandler.getId(); - this.text = sText || webFXTreeConfig.defaultText; - this.action = sAction || webFXTreeConfig.defaultAction; - this.targetWindow = sTarget || webFXTreeConfig.defaultTarget; - this._last = false; - webFXTreeHandler.all[this.id] = this; -} - -WebFXTreeAbstractNode.prototype.add = function (node) { - node.parentNode = this; - this.childNodes[this.childNodes.length] = node; - var root = this; - if (this.childNodes.length >=2) { - this.childNodes[this.childNodes.length -2]._last = false; - } - while (root.parentNode) { root = root.parentNode; } - if (root.rendered) { - if (this.childNodes.length >= 2) { - document.getElementById(this.childNodes[this.childNodes.length -2].id + '-plus').src = ((this.childNodes[this.childNodes.length -2].folder)?webFXTreeConfig.tMinusIcon:webFXTreeConfig.tIcon); - if (this.childNodes[this.childNodes.length -2].folder) { - this.childNodes[this.childNodes.length -2].plusIcon = webFXTreeConfig.tPlusIcon; - this.childNodes[this.childNodes.length -2].minusIcon = webFXTreeConfig.tMinusIcon; - } - this.childNodes[this.childNodes.length -2]._last = false; - } - this._last = true; - var foo = this; - while (foo.parentNode) { - for (var i = 0; i < foo.parentNode.childNodes.length; i++) { - if (foo.id == foo.parentNode.childNodes[i].id) { break; } - } - if (++i == foo.parentNode.childNodes.length) { foo.parentNode._last = true; } - else { foo.parentNode._last = false; } - foo = foo.parentNode; - } - document.getElementById(this.id + '-cont').insertAdjacentHTML("beforeEnd", node.toString()); - if ((!this.folder) && (!this.openIcon)) { - this.icon = webFXTreeConfig.folderIcon; - this.openIcon = webFXTreeConfig.openFolderIcon; - } - this.folder = true; - this.indent(); - this.expand(); - } - return node; -} - -WebFXTreeAbstractNode.prototype.toggle = function() { - if (this.folder) { - if (this.open) { this.collapse(); } - else { this.expand(); } - } -} - -WebFXTreeAbstractNode.prototype.select = function() { - document.getElementById(this.id + '-anchor').focus(); -} - -WebFXTreeAbstractNode.prototype.focus = function() { - webFXTreeHandler.selected = this; - if ((this.openIcon) && (webFXTreeHandler.behavior != 'classic')) { document.getElementById(this.id + '-icon').src = this.openIcon; } - document.getElementById(this.id + '-anchor').style.backgroundColor = 'highlight'; - document.getElementById(this.id + '-anchor').style.color = 'highlighttext'; - document.getElementById(this.id + '-anchor').focus(); -} - -WebFXTreeAbstractNode.prototype.blur = function() { - if ((this.openIcon) && (webFXTreeHandler.behavior != 'classic')) { document.getElementById(this.id + '-icon').src = this.icon; } - document.getElementById(this.id + '-anchor').style.backgroundColor = 'transparent'; - document.getElementById(this.id + '-anchor').style.color = 'menutext'; -} - -WebFXTreeAbstractNode.prototype.doExpand = function() { - if (webFXTreeHandler.behavior == 'classic') { document.getElementById(this.id + '-icon').src = this.openIcon; } - if (this.childNodes.length) { document.getElementById(this.id + '-cont').style.display = 'block'; } - this.open = true; - webFXTreeHandler.cookies.setCookie(this.id.substr(18,this.id.length - 18), '1'); -} - -WebFXTreeAbstractNode.prototype.doCollapse = function() { - if (webFXTreeHandler.behavior == 'classic') { document.getElementById(this.id + '-icon').src = this.icon; } - if (this.childNodes.length) { document.getElementById(this.id + '-cont').style.display = 'none'; } - this.open = false; - webFXTreeHandler.cookies.setCookie(this.id.substr(18,this.id.length - 18), '0'); -} - -WebFXTreeAbstractNode.prototype.expandAll = function() { - this.expandChildren(); - if ((this.folder) && (!this.open)) { this.expand(); } -} - -WebFXTreeAbstractNode.prototype.expandChildren = function() { - for (var i = 0; i < this.childNodes.length; i++) { - this.childNodes[i].expandAll(); -} } - -WebFXTreeAbstractNode.prototype.collapseAll = function() { - if ((this.folder) && (this.open)) { this.collapse(); } - this.collapseChildren(); -} - -WebFXTreeAbstractNode.prototype.collapseChildren = function() { - for (var i = 0; i < this.childNodes.length; i++) { - this.childNodes[i].collapseAll(); -} } - -WebFXTreeAbstractNode.prototype.indent = function(lvl, del, last, level) { - /* - * Since we only want to modify items one level below ourself, - * and since the rightmost indentation position is occupied by - * the plus icon we set this to -2 - */ - if (lvl == null) { lvl = -2; } - var state = 0; - for (var i = this.childNodes.length - 1; i >= 0 ; i--) { - state = this.childNodes[i].indent(lvl + 1, del, last, level); - if (state) { return; } - } - if (del) { - if (level >= this._level) { - if (this.folder) { - document.getElementById(this.id + '-plus').src = (this.open)?webFXTreeConfig.lMinusIcon:webFXTreeConfig.lPlusIcon; - this.plusIcon = webFXTreeConfig.lPlusIcon; - this.minusIcon = webFXTreeConfig.lMinusIcon; - } - else { document.getElementById(this.id + '-plus').src = webFXTreeConfig.lIcon; } - return 1; - } - } - var foo = document.getElementById(this.id + '-indent-' + lvl); - if (foo) { - if ((del) && (last)) { foo._last = true; } - if (foo._last) { foo.src = webFXTreeConfig.blankIcon; } - else { foo.src = webFXTreeConfig.iIcon; } - } - return 0; -} - -/* - * WebFXTree class - */ - -function WebFXTree(sText, sAction, sBehavior, sIcon, sOpenIcon) { - this.base = WebFXTreeAbstractNode; - this.base(sText, sAction); - this.icon = sIcon || webFXTreeConfig.rootIcon; - this.openIcon = sOpenIcon || webFXTreeConfig.openRootIcon; - /* Defaults to open */ - this.open = (webFXTreeHandler.cookies.getCookie(this.id.substr(18,this.id.length - 18)) == '0')?false:true; - this.folder = true; - this.rendered = false; - if (!webFXTreeHandler.behavior) { webFXTreeHandler.behavior = sBehavior || webFXTreeConfig.defaultBehavior; } - this.targetWindow = 'right'; -} - -WebFXTree.prototype = new WebFXTreeAbstractNode; - -WebFXTree.prototype.setBehavior = function (sBehavior) { - webFXTreeHandler.behavior = sBehavior; -}; - -WebFXTree.prototype.getBehavior = function (sBehavior) { - return webFXTreeHandler.behavior; -}; - -WebFXTree.prototype.getSelected = function() { - if (webFXTreeHandler.selected) { return webFXTreeHandler.selected; } - else { return null; } -} - -WebFXTree.prototype.remove = function() { } - -WebFXTree.prototype.expand = function() { - this.doExpand(); -} - -WebFXTree.prototype.collapse = function() { - this.focus(); - this.doCollapse(); -} - -WebFXTree.prototype.getFirst = function() { - return null; -} - -WebFXTree.prototype.getLast = function() { - return null; -} - -WebFXTree.prototype.getNextSibling = function() { - return null; -} - -WebFXTree.prototype.getPreviousSibling = function() { - return null; -} - -WebFXTree.prototype.keydown = function(key) { - if (key == 39) { this.expand(); return false; } - if (key == 37) { this.collapse(); return false; } - if ((key == 40) && (this.open)) { this.childNodes[0].select(); return false; } - return true; -} - -WebFXTree.prototype.toString = function() { - var str = "
    "; - str += "" + this.text + "
    "; - str += "
    "; - for (var i = 0; i < this.childNodes.length; i++) { - str += this.childNodes[i].toString(i, this.childNodes.length); - } - str += "
    "; - this.rendered = true; - return str; -}; - -/* - * WebFXTreeItem class - */ - -function WebFXTreeItem(sText, sAction, eParent, sIcon, sOpenIcon) { - this.base = WebFXTreeAbstractNode; - this.base(sText, sAction); - /* Defaults to close */ - this.open = (webFXTreeHandler.cookies.getCookie(this.id.substr(18,this.id.length - 18)) == '1')?true:false; - if (eParent) { eParent.add(this); } - if (sIcon) { this.icon = sIcon; } - if (sOpenIcon) { this.openIcon = sOpenIcon; } -} - -WebFXTreeItem.prototype = new WebFXTreeAbstractNode; - -WebFXTreeItem.prototype.remove = function() { - var parentNode = this.parentNode; - var prevSibling = this.getPreviousSibling(true); - var nextSibling = this.getNextSibling(true); - var folder = this.parentNode.folder; - var last = ((nextSibling) && (nextSibling.parentNode) && (nextSibling.parentNode.id == parentNode.id))?false:true; - this.getPreviousSibling().focus(); - this._remove(); - if (parentNode.childNodes.length == 0) { - parentNode.folder = false; - parentNode.open = false; - } - if (last) { - if (parentNode.id == prevSibling.id) { - document.getElementById(parentNode.id + '-icon').src = webFXTreeConfig.fileIcon; - } - else { } - } - if ((!prevSibling.parentNode) || (prevSibling.parentNode != parentNode)) { - parentNode.indent(null, true, last, this._level); - } - if (document.getElementById(prevSibling.id + '-plus')) { - if (nextSibling) { - if ((parentNode == prevSibling) && (parentNode.getNextSibling)) { document.getElementById(prevSibling.id + '-plus').src = webFXTreeConfig.tIcon; } - else if (nextSibling.parentNode != prevSibling) { document.getElementById(prevSibling.id + '-plus').src = webFXTreeConfig.lIcon; } - } - else { document.getElementById(prevSibling.id + '-plus').src = webFXTreeConfig.lIcon; } - } -} - -WebFXTreeItem.prototype._remove = function() { - for (var i = this.childNodes.length - 1; i >= 0; i--) { - this.childNodes[i]._remove(); - } - for (var i = 0; i < this.parentNode.childNodes.length; i++) { - if (this.id == this.parentNode.childNodes[i].id) { - for (var j = i; j < this.parentNode.childNodes.length; j++) { - this.parentNode.childNodes[i] = this.parentNode.childNodes[i+1] - } - this.parentNode.childNodes.length = this.parentNode.childNodes.length - 1; - if (i + 1 == this.parentNode.childNodes.length) { this.parentNode._last = true; } - } - } - webFXTreeHandler.all[this.id] = null; - if (document.getElementById(this.id)) { - document.getElementById(this.id).innerHTML = ""; - document.getElementById(this.id).removeNode(); - } -} - -WebFXTreeItem.prototype.expand = function() { - this.doExpand(); - document.getElementById(this.id + '-plus').src = this.minusIcon; -} - -WebFXTreeItem.prototype.collapse = function() { - this.focus(); - this.doCollapse(); - document.getElementById(this.id + '-plus').src = this.plusIcon; -} - -WebFXTreeItem.prototype.getFirst = function() { - return this.childNodes[0]; -} - -WebFXTreeItem.prototype.getLast = function() { - if (this.childNodes[this.childNodes.length - 1].open) { return this.childNodes[this.childNodes.length - 1].getLast(); } - else { return this.childNodes[this.childNodes.length - 1]; } -} - -WebFXTreeItem.prototype.getNextSibling = function() { - for (var i = 0; i < this.parentNode.childNodes.length; i++) { - if (this == this.parentNode.childNodes[i]) { break; } - } - if (++i == this.parentNode.childNodes.length) { return this.parentNode.getNextSibling(); } - else { return this.parentNode.childNodes[i]; } -} - -WebFXTreeItem.prototype.getPreviousSibling = function(b) { - for (var i = 0; i < this.parentNode.childNodes.length; i++) { - if (this == this.parentNode.childNodes[i]) { break; } - } - if (i == 0) { return this.parentNode; } - else { - if ((this.parentNode.childNodes[--i].open) || (b && this.parentNode.childNodes[i].folder)) { return this.parentNode.childNodes[i].getLast(); } - else { return this.parentNode.childNodes[i]; } -} } - -WebFXTreeItem.prototype.keydown = function(key) { - if ((key == 39) && (this.folder)) { - if (!this.open) { this.expand(); return false; } - else { this.getFirst().select(); return false; } - } - else if (key == 37) { - if (this.open) { this.collapse(); return false; } - else { this.parentNode.select(); return false; } - } - else if (key == 40) { - if (this.open) { this.getFirst().select(); return false; } - else { - var sib = this.getNextSibling(); - if (sib) { sib.select(); return false; } - } } - else if (key == 38) { this.getPreviousSibling().select(); return false; } - return true; -} - -WebFXTreeItem.prototype.toString = function (nItem, nItemCount) { - var foo = this.parentNode; - var indent = ''; - if (nItem + 1 == nItemCount) { this.parentNode._last = true; } - var i = 0; - while (foo.parentNode) { - foo = foo.parentNode; - indent = "" + indent; - i++; - } - this._level = i; - if (this.childNodes.length) { this.folder = 1; } - else { this.open = false; } - if ((this.folder) || (webFXTreeHandler.behavior != 'classic')) { - if (!this.icon) { this.icon = webFXTreeConfig.folderIcon; } - if (!this.openIcon) { this.openIcon = webFXTreeConfig.openFolderIcon; } - } - else if (!this.icon) { this.icon = webFXTreeConfig.fileIcon; } - var label = this.text; - label = label.replace('<', '<'); - label = label.replace('>', '>'); - var str = "
    "; - str += indent; - str += "" - str += "" + label + "
    "; - str += "
    "; - for (var i = 0; i < this.childNodes.length; i++) { - str += this.childNodes[i].toString(i,this.childNodes.length); - } - str += "
    "; - this.plusIcon = ((this.parentNode._last)?webFXTreeConfig.lPlusIcon:webFXTreeConfig.tPlusIcon); - this.minusIcon = ((this.parentNode._last)?webFXTreeConfig.lMinusIcon:webFXTreeConfig.tMinusIcon); - return str; -} \ No newline at end of file +/*----------------------------------------\ +| Cross Browser Tree Widget 1.1 | +|-----------------------------------------| +| Created by Emil A. Eklund (eae@eae.net) | +| For WebFX (http://webfx.eae.net/) | +|-----------------------------------------| +| This script is provided as is without | +| any warranty whatsoever. It may be used | +| free of charge for non commerical sites | +| For commerical use contact the author | +| of this script for further details. | +|-----------------------------------------| +| Created 2000-12-11 | Updated 2001-09-06 | +\----------------------------------------*/ + +var webFXTreeConfig = { + rootIcon : 'media/images/empty.png', + openRootIcon : 'media/images/empty.png', + folderIcon : 'media/images/empty.png', + openFolderIcon : 'media/images/empty.png', + fileIcon : 'media/images/empty.png', + iIcon : 'media/images/I.png', + lIcon : 'media/images/L.png', + lMinusIcon : 'media/images/Lminus.png', + lPlusIcon : 'media/images/Lplus.png', + tIcon : 'media/images/T.png', + tMinusIcon : 'media/images/Tminus.png', + tPlusIcon : 'media/images/Tplus.png', + blankIcon : 'media/images/blank.png', + defaultText : 'Tree Item', + defaultAction : 'javascript:void(0);', + defaultTarget : 'right', + defaultBehavior : 'classic' +}; + +var webFXTreeHandler = { + idCounter : 0, + idPrefix : "webfx-tree-object-", + all : {}, + behavior : null, + selected : null, + getId : function() { return this.idPrefix + this.idCounter++; }, + toggle : function (oItem) { this.all[oItem.id.replace('-plus','')].toggle(); }, + select : function (oItem) { this.all[oItem.id.replace('-icon','')].select(); }, + focus : function (oItem) { this.all[oItem.id.replace('-anchor','')].focus(); }, + blur : function (oItem) { this.all[oItem.id.replace('-anchor','')].blur(); }, + keydown : function (oItem) { return this.all[oItem.id].keydown(window.event.keyCode); }, + cookies : new WebFXCookie() +}; + +/* + * WebFXCookie class + */ + +function WebFXCookie() { + if (document.cookie.length) { this.cookies = ' ' + document.cookie; } +} + +WebFXCookie.prototype.setCookie = function (key, value) { + document.cookie = key + "=" + escape(value); +} + +WebFXCookie.prototype.getCookie = function (key) { + if (this.cookies) { + var start = this.cookies.indexOf(' ' + key + '='); + if (start == -1) { return null; } + var end = this.cookies.indexOf(";", start); + if (end == -1) { end = this.cookies.length; } + end -= start; + var cookie = this.cookies.substr(start,end); + return unescape(cookie.substr(cookie.indexOf('=') + 1, cookie.length - cookie.indexOf('=') + 1)); + } + else { return null; } +} + +/* + * WebFXTreeAbstractNode class + */ + +function WebFXTreeAbstractNode(sText, sAction, sTarget) { + this.childNodes = []; + this.id = webFXTreeHandler.getId(); + this.text = sText || webFXTreeConfig.defaultText; + this.action = sAction || webFXTreeConfig.defaultAction; + this.targetWindow = sTarget || webFXTreeConfig.defaultTarget; + this._last = false; + webFXTreeHandler.all[this.id] = this; +} + +WebFXTreeAbstractNode.prototype.add = function (node) { + node.parentNode = this; + this.childNodes[this.childNodes.length] = node; + var root = this; + if (this.childNodes.length >=2) { + this.childNodes[this.childNodes.length -2]._last = false; + } + while (root.parentNode) { root = root.parentNode; } + if (root.rendered) { + if (this.childNodes.length >= 2) { + document.getElementById(this.childNodes[this.childNodes.length -2].id + '-plus').src = ((this.childNodes[this.childNodes.length -2].folder)?webFXTreeConfig.tMinusIcon:webFXTreeConfig.tIcon); + if (this.childNodes[this.childNodes.length -2].folder) { + this.childNodes[this.childNodes.length -2].plusIcon = webFXTreeConfig.tPlusIcon; + this.childNodes[this.childNodes.length -2].minusIcon = webFXTreeConfig.tMinusIcon; + } + this.childNodes[this.childNodes.length -2]._last = false; + } + this._last = true; + var foo = this; + while (foo.parentNode) { + for (var i = 0; i < foo.parentNode.childNodes.length; i++) { + if (foo.id == foo.parentNode.childNodes[i].id) { break; } + } + if (++i == foo.parentNode.childNodes.length) { foo.parentNode._last = true; } + else { foo.parentNode._last = false; } + foo = foo.parentNode; + } + document.getElementById(this.id + '-cont').insertAdjacentHTML("beforeEnd", node.toString()); + if ((!this.folder) && (!this.openIcon)) { + this.icon = webFXTreeConfig.folderIcon; + this.openIcon = webFXTreeConfig.openFolderIcon; + } + this.folder = true; + this.indent(); + this.expand(); + } + return node; +} + +WebFXTreeAbstractNode.prototype.toggle = function() { + if (this.folder) { + if (this.open) { this.collapse(); } + else { this.expand(); } + } +} + +WebFXTreeAbstractNode.prototype.select = function() { + document.getElementById(this.id + '-anchor').focus(); +} + +WebFXTreeAbstractNode.prototype.focus = function() { + webFXTreeHandler.selected = this; + if ((this.openIcon) && (webFXTreeHandler.behavior != 'classic')) { document.getElementById(this.id + '-icon').src = this.openIcon; } + document.getElementById(this.id + '-anchor').style.backgroundColor = 'highlight'; + document.getElementById(this.id + '-anchor').style.color = 'highlighttext'; + document.getElementById(this.id + '-anchor').focus(); +} + +WebFXTreeAbstractNode.prototype.blur = function() { + if ((this.openIcon) && (webFXTreeHandler.behavior != 'classic')) { document.getElementById(this.id + '-icon').src = this.icon; } + document.getElementById(this.id + '-anchor').style.backgroundColor = 'transparent'; + document.getElementById(this.id + '-anchor').style.color = 'menutext'; +} + +WebFXTreeAbstractNode.prototype.doExpand = function() { + if (webFXTreeHandler.behavior == 'classic') { document.getElementById(this.id + '-icon').src = this.openIcon; } + if (this.childNodes.length) { document.getElementById(this.id + '-cont').style.display = 'block'; } + this.open = true; + webFXTreeHandler.cookies.setCookie(this.id.substr(18,this.id.length - 18), '1'); +} + +WebFXTreeAbstractNode.prototype.doCollapse = function() { + if (webFXTreeHandler.behavior == 'classic') { document.getElementById(this.id + '-icon').src = this.icon; } + if (this.childNodes.length) { document.getElementById(this.id + '-cont').style.display = 'none'; } + this.open = false; + webFXTreeHandler.cookies.setCookie(this.id.substr(18,this.id.length - 18), '0'); +} + +WebFXTreeAbstractNode.prototype.expandAll = function() { + this.expandChildren(); + if ((this.folder) && (!this.open)) { this.expand(); } +} + +WebFXTreeAbstractNode.prototype.expandChildren = function() { + for (var i = 0; i < this.childNodes.length; i++) { + this.childNodes[i].expandAll(); +} } + +WebFXTreeAbstractNode.prototype.collapseAll = function() { + if ((this.folder) && (this.open)) { this.collapse(); } + this.collapseChildren(); +} + +WebFXTreeAbstractNode.prototype.collapseChildren = function() { + for (var i = 0; i < this.childNodes.length; i++) { + this.childNodes[i].collapseAll(); +} } + +WebFXTreeAbstractNode.prototype.indent = function(lvl, del, last, level) { + /* + * Since we only want to modify items one level below ourself, + * and since the rightmost indentation position is occupied by + * the plus icon we set this to -2 + */ + if (lvl == null) { lvl = -2; } + var state = 0; + for (var i = this.childNodes.length - 1; i >= 0 ; i--) { + state = this.childNodes[i].indent(lvl + 1, del, last, level); + if (state) { return; } + } + if (del) { + if (level >= this._level) { + if (this.folder) { + document.getElementById(this.id + '-plus').src = (this.open)?webFXTreeConfig.lMinusIcon:webFXTreeConfig.lPlusIcon; + this.plusIcon = webFXTreeConfig.lPlusIcon; + this.minusIcon = webFXTreeConfig.lMinusIcon; + } + else { document.getElementById(this.id + '-plus').src = webFXTreeConfig.lIcon; } + return 1; + } + } + var foo = document.getElementById(this.id + '-indent-' + lvl); + if (foo) { + if ((del) && (last)) { foo._last = true; } + if (foo._last) { foo.src = webFXTreeConfig.blankIcon; } + else { foo.src = webFXTreeConfig.iIcon; } + } + return 0; +} + +/* + * WebFXTree class + */ + +function WebFXTree(sText, sAction, sBehavior, sIcon, sOpenIcon) { + this.base = WebFXTreeAbstractNode; + this.base(sText, sAction); + this.icon = sIcon || webFXTreeConfig.rootIcon; + this.openIcon = sOpenIcon || webFXTreeConfig.openRootIcon; + /* Defaults to open */ + this.open = (webFXTreeHandler.cookies.getCookie(this.id.substr(18,this.id.length - 18)) == '0')?false:true; + this.folder = true; + this.rendered = false; + if (!webFXTreeHandler.behavior) { webFXTreeHandler.behavior = sBehavior || webFXTreeConfig.defaultBehavior; } + this.targetWindow = 'right'; +} + +WebFXTree.prototype = new WebFXTreeAbstractNode; + +WebFXTree.prototype.setBehavior = function (sBehavior) { + webFXTreeHandler.behavior = sBehavior; +}; + +WebFXTree.prototype.getBehavior = function (sBehavior) { + return webFXTreeHandler.behavior; +}; + +WebFXTree.prototype.getSelected = function() { + if (webFXTreeHandler.selected) { return webFXTreeHandler.selected; } + else { return null; } +} + +WebFXTree.prototype.remove = function() { } + +WebFXTree.prototype.expand = function() { + this.doExpand(); +} + +WebFXTree.prototype.collapse = function() { + this.focus(); + this.doCollapse(); +} + +WebFXTree.prototype.getFirst = function() { + return null; +} + +WebFXTree.prototype.getLast = function() { + return null; +} + +WebFXTree.prototype.getNextSibling = function() { + return null; +} + +WebFXTree.prototype.getPreviousSibling = function() { + return null; +} + +WebFXTree.prototype.keydown = function(key) { + if (key == 39) { this.expand(); return false; } + if (key == 37) { this.collapse(); return false; } + if ((key == 40) && (this.open)) { this.childNodes[0].select(); return false; } + return true; +} + +WebFXTree.prototype.toString = function() { + var str = "
    "; + str += "" + this.text + "
    "; + str += "
    "; + for (var i = 0; i < this.childNodes.length; i++) { + str += this.childNodes[i].toString(i, this.childNodes.length); + } + str += "
    "; + this.rendered = true; + return str; +}; + +/* + * WebFXTreeItem class + */ + +function WebFXTreeItem(sText, sAction, eParent, sIcon, sOpenIcon) { + this.base = WebFXTreeAbstractNode; + this.base(sText, sAction); + /* Defaults to close */ + this.open = (webFXTreeHandler.cookies.getCookie(this.id.substr(18,this.id.length - 18)) == '1')?true:false; + if (eParent) { eParent.add(this); } + if (sIcon) { this.icon = sIcon; } + if (sOpenIcon) { this.openIcon = sOpenIcon; } +} + +WebFXTreeItem.prototype = new WebFXTreeAbstractNode; + +WebFXTreeItem.prototype.remove = function() { + var parentNode = this.parentNode; + var prevSibling = this.getPreviousSibling(true); + var nextSibling = this.getNextSibling(true); + var folder = this.parentNode.folder; + var last = ((nextSibling) && (nextSibling.parentNode) && (nextSibling.parentNode.id == parentNode.id))?false:true; + this.getPreviousSibling().focus(); + this._remove(); + if (parentNode.childNodes.length == 0) { + parentNode.folder = false; + parentNode.open = false; + } + if (last) { + if (parentNode.id == prevSibling.id) { + document.getElementById(parentNode.id + '-icon').src = webFXTreeConfig.fileIcon; + } + else { } + } + if ((!prevSibling.parentNode) || (prevSibling.parentNode != parentNode)) { + parentNode.indent(null, true, last, this._level); + } + if (document.getElementById(prevSibling.id + '-plus')) { + if (nextSibling) { + if ((parentNode == prevSibling) && (parentNode.getNextSibling)) { document.getElementById(prevSibling.id + '-plus').src = webFXTreeConfig.tIcon; } + else if (nextSibling.parentNode != prevSibling) { document.getElementById(prevSibling.id + '-plus').src = webFXTreeConfig.lIcon; } + } + else { document.getElementById(prevSibling.id + '-plus').src = webFXTreeConfig.lIcon; } + } +} + +WebFXTreeItem.prototype._remove = function() { + for (var i = this.childNodes.length - 1; i >= 0; i--) { + this.childNodes[i]._remove(); + } + for (var i = 0; i < this.parentNode.childNodes.length; i++) { + if (this.id == this.parentNode.childNodes[i].id) { + for (var j = i; j < this.parentNode.childNodes.length; j++) { + this.parentNode.childNodes[i] = this.parentNode.childNodes[i+1] + } + this.parentNode.childNodes.length = this.parentNode.childNodes.length - 1; + if (i + 1 == this.parentNode.childNodes.length) { this.parentNode._last = true; } + } + } + webFXTreeHandler.all[this.id] = null; + if (document.getElementById(this.id)) { + document.getElementById(this.id).innerHTML = ""; + document.getElementById(this.id).removeNode(); + } +} + +WebFXTreeItem.prototype.expand = function() { + this.doExpand(); + document.getElementById(this.id + '-plus').src = this.minusIcon; +} + +WebFXTreeItem.prototype.collapse = function() { + this.focus(); + this.doCollapse(); + document.getElementById(this.id + '-plus').src = this.plusIcon; +} + +WebFXTreeItem.prototype.getFirst = function() { + return this.childNodes[0]; +} + +WebFXTreeItem.prototype.getLast = function() { + if (this.childNodes[this.childNodes.length - 1].open) { return this.childNodes[this.childNodes.length - 1].getLast(); } + else { return this.childNodes[this.childNodes.length - 1]; } +} + +WebFXTreeItem.prototype.getNextSibling = function() { + for (var i = 0; i < this.parentNode.childNodes.length; i++) { + if (this == this.parentNode.childNodes[i]) { break; } + } + if (++i == this.parentNode.childNodes.length) { return this.parentNode.getNextSibling(); } + else { return this.parentNode.childNodes[i]; } +} + +WebFXTreeItem.prototype.getPreviousSibling = function(b) { + for (var i = 0; i < this.parentNode.childNodes.length; i++) { + if (this == this.parentNode.childNodes[i]) { break; } + } + if (i == 0) { return this.parentNode; } + else { + if ((this.parentNode.childNodes[--i].open) || (b && this.parentNode.childNodes[i].folder)) { return this.parentNode.childNodes[i].getLast(); } + else { return this.parentNode.childNodes[i]; } +} } + +WebFXTreeItem.prototype.keydown = function(key) { + if ((key == 39) && (this.folder)) { + if (!this.open) { this.expand(); return false; } + else { this.getFirst().select(); return false; } + } + else if (key == 37) { + if (this.open) { this.collapse(); return false; } + else { this.parentNode.select(); return false; } + } + else if (key == 40) { + if (this.open) { this.getFirst().select(); return false; } + else { + var sib = this.getNextSibling(); + if (sib) { sib.select(); return false; } + } } + else if (key == 38) { this.getPreviousSibling().select(); return false; } + return true; +} + +WebFXTreeItem.prototype.toString = function (nItem, nItemCount) { + var foo = this.parentNode; + var indent = ''; + if (nItem + 1 == nItemCount) { this.parentNode._last = true; } + var i = 0; + while (foo.parentNode) { + foo = foo.parentNode; + indent = "" + indent; + i++; + } + this._level = i; + if (this.childNodes.length) { this.folder = 1; } + else { this.open = false; } + if ((this.folder) || (webFXTreeHandler.behavior != 'classic')) { + if (!this.icon) { this.icon = webFXTreeConfig.folderIcon; } + if (!this.openIcon) { this.openIcon = webFXTreeConfig.openFolderIcon; } + } + else if (!this.icon) { this.icon = webFXTreeConfig.fileIcon; } + var label = this.text; + label = label.replace('<', '<'); + label = label.replace('>', '>'); + var str = "
    "; + str += indent; + str += "" + str += "" + label + "
    "; + str += "
    "; + for (var i = 0; i < this.childNodes.length; i++) { + str += this.childNodes[i].toString(i,this.childNodes.length); + } + str += "
    "; + this.plusIcon = ((this.parentNode._last)?webFXTreeConfig.lPlusIcon:webFXTreeConfig.tPlusIcon); + this.minusIcon = ((this.parentNode._last)?webFXTreeConfig.lMinusIcon:webFXTreeConfig.tMinusIcon); + return str; +} diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/media/stylesheet.css b/sites/all/modules/contrib/dev/devel/krumo/docs/media/stylesheet.css old mode 100755 new mode 100644 index 498826a..df89aff --- a/sites/all/modules/contrib/dev/devel/krumo/docs/media/stylesheet.css +++ b/sites/all/modules/contrib/dev/devel/krumo/docs/media/stylesheet.css @@ -1,181 +1,181 @@ -a { color: #000090; text-decoration: none; } -a:hover, a:active, a:focus { color: highlighttext; background-color: highlight; text-decoration: none; } - -body { background : #FFFFFF; } -body, table { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 10pt; } - -a img { border: 0px; } - -/* Page layout/boxes */ - -.info-box { } -.info-box-title { margin: 1em 0em 0em 0em; font-weight: normal; font-size: 14pt; color: #999999; border-bottom: 2px solid #999999; } -.info-box-body { border: 1px solid #999999; padding: .5em; } -.nav-bar { font-size: 8pt; white-space: nowrap; text-align: right; padding: .2em; margin: 0em 0em 1em 0em; } - -.oddrow { background-color: #F8F8F8; border: 1px solid #AAAAAA; padding: .5em; margin-bottom: 1em} -.evenrow { border: 1px solid #AAAAAA; padding: .5em; margin-bottom: 1em} - -.page-body { max-width: 800px; margin: auto; } -.tree { } - -/* Index formatting classes */ - -.index-item-body { margin-top: .5em; margin-bottom: .5em} -.index-item-description { margin-top: .25em } -.index-item-details { font-weight: normal; font-style: italic; font-size: 8pt } -.index-letter-section { background-color: #EEEEEE; border: 1px dotted #999999; padding: .5em; margin-bottom: 1em} -.index-letter-title { font-size: 12pt; font-weight: bold } -.index-letter-menu { text-align: center; margin: 1em } -.index-letter { font-size: 12pt } - -/* Docbook classes */ - -.description {} -.short-description { font-weight: bold; color: #666666; } -.tags { padding-left: 0em; margin-left: 3em; color: #666666; list-style-type: square; } -.parameters { padding-left: 0em; margin-left: 3em; color: #014fbe; list-style-type: square; } -.redefinitions { font-size: 8pt; padding-left: 0em; margin-left: 2em; } -.package { font-weight: bold; } -.package-title { font-weight: bold; font-size: 14pt; border-bottom: 1px solid black } -.sub-package { font-weight: bold; } -.tutorial { border-width: thin; border-color: #0066ff; } -.tutorial-nav-box { width: 100%; border: 1px solid #999999; background-color: #F8F8F8; } - -/* Generic formatting */ - -.field { font-weight: bold; } -.detail { font-size: 8pt; } -.notes { font-style: italic; font-size: 8pt; } -.separator { background-color: #999999; height: 2px; } -.warning { color: #FF6600; } -.disabled { font-style: italic; color: #999999; } - -/* Code elements */ - -.line-number { } - -.class-table { width: 100%; } -.class-table-header { border-bottom: 1px dotted #666666; text-align: left} -.class-name { color: #0000AA; font-weight: bold; } - -.method-summary { color: #009000; padding-left: 1em; font-size: 8pt; } -.method-header { } -.method-definition { margin-bottom: .2em } -.method-title { color: #009000; font-weight: bold; } -.method-name { font-weight: bold; } -.method-signature { font-size: 85%; color: #666666; margin: .5em 0em } -.method-result { font-style: italic; } - -.var-summary { padding-left: 1em; font-size: 8pt; } -.var-header { } -.var-title { color: #014fbe; margin-bottom: .3em } -.var-type { font-style: italic; } -.var-name { font-weight: bold; } -.var-default {} -.var-description { font-weight: normal; color: #000000; } - -.include-title { color: #014fbe;} -.include-type { font-style: italic; } -.include-name { font-weight: bold; } - -.const-title { color: #FF6600; } -.const-name { font-weight: bold; } - -/* Syntax highlighting */ - -.src-code { font-family: 'Courier New', Courier, monospace; font-weight: normal; } -.src-line { font-family: 'Courier New', Courier, monospace; font-weight: normal; } - -.src-code a:link { padding: 1px; text-decoration: underline; color: #0000DD; } -.src-code a:visited { text-decoration: underline; color: #0000DD; } -.src-code a:active { background-color: #FFFF66; color: #008000; } -.src-code a:hover { background-color: #FFFF66; text-decoration: overline underline; color: #008000; } - -.src-comm { color: #666666; } -.src-id { color: #FF6600; font-style: italic; } -.src-inc { color: #0000AA; font-weight: bold; } -.src-key { color: #0000AA; font-weight: bold; } -.src-num { color: #CC0000; } -.src-str { color: #CC0000; } -.src-sym { } -.src-var { } - -.src-php { font-weight: bold; } - -.src-doc { color: #666666; } -.src-doc-close-template { color: #666666 } -.src-doc-coretag { color: #008000; } -.src-doc-inlinetag {} -.src-doc-internal {} -.src-doc-tag { color: #0080CC; } -.src-doc-template { color: #666666 } -.src-doc-type { font-style: italic; color: #444444 } -.src-doc-var { color: #444444 } - -.tute-tag { color: #009999 } -.tute-attribute-name { color: #0000FF } -.tute-attribute-value { color: #0099FF } -.tute-entity { font-weight: bold; } -.tute-comment { font-style: italic } -.tute-inline-tag { color: #636311; font-weight: bold } - -/* tutorial */ - -.authors { } -.author { font-style: italic; font-weight: bold } -.author-blurb { margin: .5em 0em .5em 2em; font-size: 85%; font-weight: normal; font-style: normal } -.example { border: 1px dashed #999999; background-color: #EEEEEE; padding: .5em; } -.listing { border: 1px dashed #999999; background-color: #EEEEEE; padding: .5em; white-space: nowrap; } -.release-info { font-size: 85%; font-style: italic; margin: 1em 0em } -.ref-title-box { } -.ref-title { } -.ref-purpose { font-style: italic; color: #666666 } -.ref-synopsis { } -.title { font-weight: bold; border-bottom: 1px solid #999999; color: #999999; } -.cmd-synopsis { margin: 1em 0em } -.cmd-title { font-weight: bold } -.toc { margin-left: 2em; padding-left: 0em } - -/*------------------------------------------------------------------------------ - webfx-tree -------------------------------------------------------------------------------*/ - -.webfx-tree-container { - margin: 0px; - padding: 0px; - white-space: nowrap; - font: icon; -} - -.webfx-tree-item { - padding: 0px; - margin: 0px; - color: black; - white-space: nowrap; - font: icon; -} - -.webfx-tree-item a { - margin-left: 3px; - padding: 1px 2px 1px 2px; - color: black; - text-decoration: none; -} - -.webfx-tree-item a:hover, .webfx-tree-item a:active { - color: highlighttext; - background: highlight; - text-decoration: none -} - -.webfx-tree-item img { - vertical-align: middle; - border: 0px; -} - -.webfx-tree-icon { - width: 16px; - height: 16px; -} - +a { color: #000090; text-decoration: none; } +a:hover, a:active, a:focus { color: highlighttext; background-color: highlight; text-decoration: none; } + +body { background : #FFFFFF; } +body, table { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 10pt; } + +a img { border: 0px; } + +/* Page layout/boxes */ + +.info-box { } +.info-box-title { margin: 1em 0em 0em 0em; font-weight: normal; font-size: 14pt; color: #999999; border-bottom: 2px solid #999999; } +.info-box-body { border: 1px solid #999999; padding: .5em; } +.nav-bar { font-size: 8pt; white-space: nowrap; text-align: right; padding: .2em; margin: 0em 0em 1em 0em; } + +.oddrow { background-color: #F8F8F8; border: 1px solid #AAAAAA; padding: .5em; margin-bottom: 1em} +.evenrow { border: 1px solid #AAAAAA; padding: .5em; margin-bottom: 1em} + +.page-body { max-width: 800px; margin: auto; } +.tree { } + +/* Index formatting classes */ + +.index-item-body { margin-top: .5em; margin-bottom: .5em} +.index-item-description { margin-top: .25em } +.index-item-details { font-weight: normal; font-style: italic; font-size: 8pt } +.index-letter-section { background-color: #EEEEEE; border: 1px dotted #999999; padding: .5em; margin-bottom: 1em} +.index-letter-title { font-size: 12pt; font-weight: bold } +.index-letter-menu { text-align: center; margin: 1em } +.index-letter { font-size: 12pt } + +/* Docbook classes */ + +.description {} +.short-description { font-weight: bold; color: #666666; } +.tags { padding-left: 0em; margin-left: 3em; color: #666666; list-style-type: square; } +.parameters { padding-left: 0em; margin-left: 3em; color: #014fbe; list-style-type: square; } +.redefinitions { font-size: 8pt; padding-left: 0em; margin-left: 2em; } +.package { font-weight: bold; } +.package-title { font-weight: bold; font-size: 14pt; border-bottom: 1px solid black } +.sub-package { font-weight: bold; } +.tutorial { border-width: thin; border-color: #0066ff; } +.tutorial-nav-box { width: 100%; border: 1px solid #999999; background-color: #F8F8F8; } + +/* Generic formatting */ + +.field { font-weight: bold; } +.detail { font-size: 8pt; } +.notes { font-style: italic; font-size: 8pt; } +.separator { background-color: #999999; height: 2px; } +.warning { color: #FF6600; } +.disabled { font-style: italic; color: #999999; } + +/* Code elements */ + +.line-number { } + +.class-table { width: 100%; } +.class-table-header { border-bottom: 1px dotted #666666; text-align: left} +.class-name { color: #0000AA; font-weight: bold; } + +.method-summary { color: #009000; padding-left: 1em; font-size: 8pt; } +.method-header { } +.method-definition { margin-bottom: .2em } +.method-title { color: #009000; font-weight: bold; } +.method-name { font-weight: bold; } +.method-signature { font-size: 85%; color: #666666; margin: .5em 0em } +.method-result { font-style: italic; } + +.var-summary { padding-left: 1em; font-size: 8pt; } +.var-header { } +.var-title { color: #014fbe; margin-bottom: .3em } +.var-type { font-style: italic; } +.var-name { font-weight: bold; } +.var-default {} +.var-description { font-weight: normal; color: #000000; } + +.include-title { color: #014fbe;} +.include-type { font-style: italic; } +.include-name { font-weight: bold; } + +.const-title { color: #FF6600; } +.const-name { font-weight: bold; } + +/* Syntax highlighting */ + +.src-code { font-family: 'Courier New', Courier, monospace; font-weight: normal; } +.src-line { font-family: 'Courier New', Courier, monospace; font-weight: normal; } + +.src-code a:link { padding: 1px; text-decoration: underline; color: #0000DD; } +.src-code a:visited { text-decoration: underline; color: #0000DD; } +.src-code a:active { background-color: #FFFF66; color: #008000; } +.src-code a:hover { background-color: #FFFF66; text-decoration: overline underline; color: #008000; } + +.src-comm { color: #666666; } +.src-id { color: #FF6600; font-style: italic; } +.src-inc { color: #0000AA; font-weight: bold; } +.src-key { color: #0000AA; font-weight: bold; } +.src-num { color: #CC0000; } +.src-str { color: #CC0000; } +.src-sym { } +.src-var { } + +.src-php { font-weight: bold; } + +.src-doc { color: #666666; } +.src-doc-close-template { color: #666666 } +.src-doc-coretag { color: #008000; } +.src-doc-inlinetag {} +.src-doc-internal {} +.src-doc-tag { color: #0080CC; } +.src-doc-template { color: #666666 } +.src-doc-type { font-style: italic; color: #444444 } +.src-doc-var { color: #444444 } + +.tute-tag { color: #009999 } +.tute-attribute-name { color: #0000FF } +.tute-attribute-value { color: #0099FF } +.tute-entity { font-weight: bold; } +.tute-comment { font-style: italic } +.tute-inline-tag { color: #636311; font-weight: bold } + +/* tutorial */ + +.authors { } +.author { font-style: italic; font-weight: bold } +.author-blurb { margin: .5em 0em .5em 2em; font-size: 85%; font-weight: normal; font-style: normal } +.example { border: 1px dashed #999999; background-color: #EEEEEE; padding: .5em; } +.listing { border: 1px dashed #999999; background-color: #EEEEEE; padding: .5em; white-space: nowrap; } +.release-info { font-size: 85%; font-style: italic; margin: 1em 0em } +.ref-title-box { } +.ref-title { } +.ref-purpose { font-style: italic; color: #666666 } +.ref-synopsis { } +.title { font-weight: bold; border-bottom: 1px solid #999999; color: #999999; } +.cmd-synopsis { margin: 1em 0em } +.cmd-title { font-weight: bold } +.toc { margin-left: 2em; padding-left: 0em } + +/*------------------------------------------------------------------------------ + webfx-tree +------------------------------------------------------------------------------*/ + +.webfx-tree-container { + margin: 0px; + padding: 0px; + white-space: nowrap; + font: icon; +} + +.webfx-tree-item { + padding: 0px; + margin: 0px; + color: black; + white-space: nowrap; + font: icon; +} + +.webfx-tree-item a { + margin-left: 3px; + padding: 1px 2px 1px 2px; + color: black; + text-decoration: none; +} + +.webfx-tree-item a:hover, .webfx-tree-item a:active { + color: highlighttext; + background: highlight; + text-decoration: none +} + +.webfx-tree-item img { + vertical-align: middle; + border: 0px; +} + +.webfx-tree-icon { + width: 16px; + height: 16px; +} + diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/packages.html b/sites/all/modules/contrib/dev/devel/krumo/docs/packages.html old mode 100755 new mode 100644 index a4852e3..c1ec015 --- a/sites/all/modules/contrib/dev/devel/krumo/docs/packages.html +++ b/sites/all/modules/contrib/dev/devel/krumo/docs/packages.html @@ -1,40 +1,40 @@ - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/ric_INSTALL.html b/sites/all/modules/contrib/dev/devel/krumo/docs/ric_INSTALL.html old mode 100755 new mode 100644 index 8851703..1b6e70b --- a/sites/all/modules/contrib/dev/devel/krumo/docs/ric_INSTALL.html +++ b/sites/all/modules/contrib/dev/devel/krumo/docs/ric_INSTALL.html @@ -1,49 +1,50 @@ - - - - - - - - - -

    INSTALL

    -
    -------------------------------------------------------------------------------
    -
    -                     SETUP: How to install Krumo ?
    -
    -------------------------------------------------------------------------------
    -
    -In order to use Krumo you have to put it on your (development) server, and 
    -include it in your script. You can put it somewhere in the INCLUDE_PATH, or 
    -specify the full path to the "class.krumo.php" file.
    -
    -You have to modify the "krumo.ini" file too. It is the configuration file for 
    -Krumo. The first option is choosing a skin:
    -
    - [skin]
    - selected = "orange"
    -
    -The value for this setting has to be the name of one of the sub-folders from the 
    -"Krumo/skins/" folder. If the value provided for the skin results in not finding 
    -the skin, the `default` skin will be used instead.
    -
    -The second option is used to set the correct web path to the folder where Krumo 
    -is installed. This is used in order to make the images from Krumo's CSS skins 
    -web-accessible.
    -
    - [css]
    - url = "http://www.example.com/Krumo/"
    -
    -So far those two are the only configuration options.
    -
    -All the CSS files ("skin.css") from the "Krumo/skins/" sub-folders must have the 
    -proper permissions in order to be readable from Krumo. Same applies for 
    -"krumo.ini" and "krumo.js" files.
    -
    -

    - Documentation generated on Sun, 02 Dec 2007 09:43:22 +0200 by phpDocumentor 1.4.0a2 -

    - - \ No newline at end of file + + + + + + + + + + +

    INSTALL

    +
    +------------------------------------------------------------------------------
    +
    +                     SETUP: How to install Krumo ?
    +
    +------------------------------------------------------------------------------
    +
    +In order to use Krumo you have to put it on your (development) server, and
    +include it in your script. You can put it somewhere in the INCLUDE_PATH, or
    +specify the full path to the "class.krumo.php" file.
    +
    +You have to modify the "krumo.ini" file too. It is the configuration file for
    +Krumo. The first option is choosing a skin:
    +
    + [skin]
    + selected = "orange"
    +
    +The value for this setting has to be the name of one of the sub-folders from the
    +"Krumo/skins/" folder. If the value provided for the skin results in not finding
    +the skin, the `default` skin will be used instead.
    +
    +The second option is used to set the correct web path to the folder where Krumo
    +is installed. This is used in order to make the images from Krumo's CSS skins
    +web-accessible.
    +
    + [css]
    + url = "http://www.example.com/Krumo/"
    +
    +So far those two are the only configuration options.
    +
    +All the CSS files ("skin.css") from the "Krumo/skins/" sub-folders must have the
    +proper permissions in order to be readable from Krumo. Same applies for
    +"krumo.ini" and "krumo.js" files.
    +
    +

    + Documentation generated on Sun, 02 Dec 2007 09:43:22 +0200 by phpDocumentor 1.4.0a2 +

    + + diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/ric_LICENSE.html b/sites/all/modules/contrib/dev/devel/krumo/docs/ric_LICENSE.html old mode 100755 new mode 100644 index aa2cad1..ec72a25 --- a/sites/all/modules/contrib/dev/devel/krumo/docs/ric_LICENSE.html +++ b/sites/all/modules/contrib/dev/devel/krumo/docs/ric_LICENSE.html @@ -1,522 +1,523 @@ - - - - - - - - - -

    LICENSE

    -
    -		  GNU LESSER GENERAL PUBLIC LICENSE
    -		       Version 2.1, February 1999
    -
    - Copyright (C) 1991, 1999 Free Software Foundation, Inc.
    -     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
    - Everyone is permitted to copy and distribute verbatim copies
    - of this license document, but changing it is not allowed.
    -
    -[This is the first released version of the Lesser GPL.  It also counts
    - as the successor of the GNU Library Public License, version 2, hence
    - the version number 2.1.]
    -
    -			    Preamble
    -
    -  The licenses for most software are designed to take away your
    -freedom to share and change it.  By contrast, the GNU General Public
    -Licenses are intended to guarantee your freedom to share and change
    -free software--to make sure the software is free for all its users.
    -
    -  This license, the Lesser General Public License, applies to some
    -specially designated software packages--typically libraries--of the
    -Free Software Foundation and other authors who decide to use it.  You
    -can use it too, but we suggest you first think carefully about whether
    -this license or the ordinary General Public License is the better
    -strategy to use in any particular case, based on the explanations below.
    -
    -  When we speak of free software, we are referring to freedom of use,
    -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 and use pieces of
    -it in new free programs; and that you are informed that you can do
    -these things.
    -
    -  To protect your rights, we need to make restrictions that forbid
    -distributors to deny you these rights or to ask you to surrender these
    -rights.  These restrictions translate to certain responsibilities for
    -you if you distribute copies of the library or if you modify it.
    -
    -  For example, if you distribute copies of the library, whether gratis
    -or for a fee, you must give the recipients all the rights that we gave
    -you.  You must make sure that they, too, receive or can get the source
    -code.  If you link other code with the library, you must provide
    -complete object files to the recipients, so that they can relink them
    -with the library after making changes to the library and recompiling
    -it.  And you must show them these terms so they know their rights.
    -
    -  We protect your rights with a two-step method: (1) we copyright the
    -library, and (2) we offer you this license, which gives you legal
    -permission to copy, distribute and/or modify the library.
    -
    -  To protect each distributor, we want to make it very clear that
    -there is no warranty for the free library.  Also, if the library is
    -modified by someone else and passed on, the recipients should know
    -that what they have is not the original version, so that the original
    -author's reputation will not be affected by problems that might be
    -introduced by others.
    -
    -  Finally, software patents pose a constant threat to the existence of
    -any free program.  We wish to make sure that a company cannot
    -effectively restrict the users of a free program by obtaining a
    -restrictive license from a patent holder.  Therefore, we insist that
    -any patent license obtained for a version of the library must be
    -consistent with the full freedom of use specified in this license.
    -
    -  Most GNU software, including some libraries, is covered by the
    -ordinary GNU General Public License.  This license, the GNU Lesser
    -General Public License, applies to certain designated libraries, and
    -is quite different from the ordinary General Public License.  We use
    -this license for certain libraries in order to permit linking those
    -libraries into non-free programs.
    -
    -  When a program is linked with a library, whether statically or using
    -a shared library, the combination of the two is legally speaking a
    -combined work, a derivative of the original library.  The ordinary
    -General Public License therefore permits such linking only if the
    -entire combination fits its criteria of freedom.  The Lesser General
    -Public License permits more lax criteria for linking other code with
    -the library.
    -
    -  We call this license the "Lesser" General Public License because it
    -does Less to protect the user's freedom than the ordinary General
    -Public License.  It also provides other free software developers Less
    -of an advantage over competing non-free programs.  These disadvantages
    -are the reason we use the ordinary General Public License for many
    -libraries.  However, the Lesser license provides advantages in certain
    -special circumstances.
    -
    -  For example, on rare occasions, there may be a special need to
    -encourage the widest possible use of a certain library, so that it becomes
    -a de-facto standard.  To achieve this, non-free programs must be
    -allowed to use the library.  A more frequent case is that a free
    -library does the same job as widely used non-free libraries.  In this
    -case, there is little to gain by limiting the free library to free
    -software only, so we use the Lesser General Public License.
    -
    -  In other cases, permission to use a particular library in non-free
    -programs enables a greater number of people to use a large body of
    -free software.  For example, permission to use the GNU C Library in
    -non-free programs enables many more people to use the whole GNU
    -operating system, as well as its variant, the GNU/Linux operating
    -system.
    -
    -  Although the Lesser General Public License is Less protective of the
    -users' freedom, it does ensure that the user of a program that is
    -linked with the Library has the freedom and the wherewithal to run
    -that program using a modified version of the Library.
    -
    -  The precise terms and conditions for copying, distribution and
    -modification follow.  Pay close attention to the difference between a
    -"work based on the library" and a "work that uses the library".  The
    -former contains code derived from the library, whereas the latter must
    -be combined with the library in order to run.
    -
    -		  GNU LESSER GENERAL PUBLIC LICENSE
    -   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
    -
    -  0. This License Agreement applies to any software library or other
    -program which contains a notice placed by the copyright holder or
    -other authorized party saying it may be distributed under the terms of
    -this Lesser General Public License (also called "this License").
    -Each licensee is addressed as "you".
    -
    -  A "library" means a collection of software functions and/or data
    -prepared so as to be conveniently linked with application programs
    -(which use some of those functions and data) to form executables.
    -
    -  The "Library", below, refers to any such software library or work
    -which has been distributed under these terms.  A "work based on the
    -Library" means either the Library or any derivative work under
    -copyright law: that is to say, a work containing the Library or a
    -portion of it, either verbatim or with modifications and/or translated
    -straightforwardly into another language.  (Hereinafter, translation is
    -included without limitation in the term "modification".)
    -
    -  "Source code" for a work means the preferred form of the work for
    -making modifications to it.  For a library, 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 library.
    -
    -  Activities other than copying, distribution and modification are not
    -covered by this License; they are outside its scope.  The act of
    -running a program using the Library is not restricted, and output from
    -such a program is covered only if its contents constitute a work based
    -on the Library (independent of the use of the Library in a tool for
    -writing it).  Whether that is true depends on what the Library does
    -and what the program that uses the Library does.
    -  
    -  1. You may copy and distribute verbatim copies of the Library's
    -complete 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 distribute a copy of this License along with the
    -Library.
    -
    -  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 Library or any portion
    -of it, thus forming a work based on the Library, 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) The modified work must itself be a software library.
    -
    -    b) You must cause the files modified to carry prominent notices
    -    stating that you changed the files and the date of any change.
    -
    -    c) You must cause the whole of the work to be licensed at no
    -    charge to all third parties under the terms of this License.
    -
    -    d) If a facility in the modified Library refers to a function or a
    -    table of data to be supplied by an application program that uses
    -    the facility, other than as an argument passed when the facility
    -    is invoked, then you must make a good faith effort to ensure that,
    -    in the event an application does not supply such function or
    -    table, the facility still operates, and performs whatever part of
    -    its purpose remains meaningful.
    -
    -    (For example, a function in a library to compute square roots has
    -    a purpose that is entirely well-defined independent of the
    -    application.  Therefore, Subsection 2d requires that any
    -    application-supplied function or table used by this function must
    -    be optional: if the application does not supply it, the square
    -    root function must still compute square roots.)
    -
    -These requirements apply to the modified work as a whole.  If
    -identifiable sections of that work are not derived from the Library,
    -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 Library, 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 Library.
    -
    -In addition, mere aggregation of another work not based on the Library
    -with the Library (or with a work based on the Library) on a volume of
    -a storage or distribution medium does not bring the other work under
    -the scope of this License.
    -
    -  3. You may opt to apply the terms of the ordinary GNU General Public
    -License instead of this License to a given copy of the Library.  To do
    -this, you must alter all the notices that refer to this License, so
    -that they refer to the ordinary GNU General Public License, version 2,
    -instead of to this License.  (If a newer version than version 2 of the
    -ordinary GNU General Public License has appeared, then you can specify
    -that version instead if you wish.)  Do not make any other change in
    -these notices.
    -
    -  Once this change is made in a given copy, it is irreversible for
    -that copy, so the ordinary GNU General Public License applies to all
    -subsequent copies and derivative works made from that copy.
    -
    -  This option is useful when you wish to copy part of the code of
    -the Library into a program that is not a library.
    -
    -  4. You may copy and distribute the Library (or a portion or
    -derivative of it, under Section 2) in object code or executable form
    -under the terms of Sections 1 and 2 above provided that you 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.
    -
    -  If distribution of 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 satisfies the requirement to
    -distribute the source code, even though third parties are not
    -compelled to copy the source along with the object code.
    -
    -  5. A program that contains no derivative of any portion of the
    -Library, but is designed to work with the Library by being compiled or
    -linked with it, is called a "work that uses the Library".  Such a
    -work, in isolation, is not a derivative work of the Library, and
    -therefore falls outside the scope of this License.
    -
    -  However, linking a "work that uses the Library" with the Library
    -creates an executable that is a derivative of the Library (because it
    -contains portions of the Library), rather than a "work that uses the
    -library".  The executable is therefore covered by this License.
    -Section 6 states terms for distribution of such executables.
    -
    -  When a "work that uses the Library" uses material from a header file
    -that is part of the Library, the object code for the work may be a
    -derivative work of the Library even though the source code is not.
    -Whether this is true is especially significant if the work can be
    -linked without the Library, or if the work is itself a library.  The
    -threshold for this to be true is not precisely defined by law.
    -
    -  If such an object file uses only numerical parameters, data
    -structure layouts and accessors, and small macros and small inline
    -functions (ten lines or less in length), then the use of the object
    -file is unrestricted, regardless of whether it is legally a derivative
    -work.  (Executables containing this object code plus portions of the
    -Library will still fall under Section 6.)
    -
    -  Otherwise, if the work is a derivative of the Library, you may
    -distribute the object code for the work under the terms of Section 6.
    -Any executables containing that work also fall under Section 6,
    -whether or not they are linked directly with the Library itself.
    -
    -  6. As an exception to the Sections above, you may also combine or
    -link a "work that uses the Library" with the Library to produce a
    -work containing portions of the Library, and distribute that work
    -under terms of your choice, provided that the terms permit
    -modification of the work for the customer's own use and reverse
    -engineering for debugging such modifications.
    -
    -  You must give prominent notice with each copy of the work that the
    -Library is used in it and that the Library and its use are covered by
    -this License.  You must supply a copy of this License.  If the work
    -during execution displays copyright notices, you must include the
    -copyright notice for the Library among them, as well as a reference
    -directing the user to the copy of this License.  Also, you must do one
    -of these things:
    -
    -    a) Accompany the work with the complete corresponding
    -    machine-readable source code for the Library including whatever
    -    changes were used in the work (which must be distributed under
    -    Sections 1 and 2 above); and, if the work is an executable linked
    -    with the Library, with the complete machine-readable "work that
    -    uses the Library", as object code and/or source code, so that the
    -    user can modify the Library and then relink to produce a modified
    -    executable containing the modified Library.  (It is understood
    -    that the user who changes the contents of definitions files in the
    -    Library will not necessarily be able to recompile the application
    -    to use the modified definitions.)
    -
    -    b) Use a suitable shared library mechanism for linking with the
    -    Library.  A suitable mechanism is one that (1) uses at run time a
    -    copy of the library already present on the user's computer system,
    -    rather than copying library functions into the executable, and (2)
    -    will operate properly with a modified version of the library, if
    -    the user installs one, as long as the modified version is
    -    interface-compatible with the version that the work was made with.
    -
    -    c) Accompany the work with a written offer, valid for at
    -    least three years, to give the same user the materials
    -    specified in Subsection 6a, above, for a charge no more
    -    than the cost of performing this distribution.
    -
    -    d) If distribution of the work is made by offering access to copy
    -    from a designated place, offer equivalent access to copy the above
    -    specified materials from the same place.
    -
    -    e) Verify that the user has already received a copy of these
    -    materials or that you have already sent this user a copy.
    -
    -  For an executable, the required form of the "work that uses the
    -Library" must include any data and utility programs needed for
    -reproducing the executable from it.  However, as a special exception,
    -the materials to be 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.
    -
    -  It may happen that this requirement contradicts the license
    -restrictions of other proprietary libraries that do not normally
    -accompany the operating system.  Such a contradiction means you cannot
    -use both them and the Library together in an executable that you
    -distribute.
    -
    -  7. You may place library facilities that are a work based on the
    -Library side-by-side in a single library together with other library
    -facilities not covered by this License, and distribute such a combined
    -library, provided that the separate distribution of the work based on
    -the Library and of the other library facilities is otherwise
    -permitted, and provided that you do these two things:
    -
    -    a) Accompany the combined library with a copy of the same work
    -    based on the Library, uncombined with any other library
    -    facilities.  This must be distributed under the terms of the
    -    Sections above.
    -
    -    b) Give prominent notice with the combined library of the fact
    -    that part of it is a work based on the Library, and explaining
    -    where to find the accompanying uncombined form of the same work.
    -
    -  8. You may not copy, modify, sublicense, link with, or distribute
    -the Library except as expressly provided under this License.  Any
    -attempt otherwise to copy, modify, sublicense, link with, or
    -distribute the Library 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.
    -
    -  9. 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 Library or its derivative works.  These actions are
    -prohibited by law if you do not accept this License.  Therefore, by
    -modifying or distributing the Library (or any work based on the
    -Library), you indicate your acceptance of this License to do so, and
    -all its terms and conditions for copying, distributing or modifying
    -the Library or works based on it.
    -
    -  10. Each time you redistribute the Library (or any work based on the
    -Library), the recipient automatically receives a license from the
    -original licensor to copy, distribute, link with or modify the Library
    -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 with
    -this License.
    -
    -  11. 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 Library at all.  For example, if a patent
    -license would not permit royalty-free redistribution of the Library 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 Library.
    -
    -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.
    -
    -  12. If the distribution and/or use of the Library is restricted in
    -certain countries either by patents or by copyrighted interfaces, the
    -original copyright holder who places the Library 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.
    -
    -  13. The Free Software Foundation may publish revised and/or new
    -versions of the Lesser 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 Library
    -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 Library does not specify a
    -license version number, you may choose any version ever published by
    -the Free Software Foundation.
    -
    -  14. If you wish to incorporate parts of the Library into other free
    -programs whose distribution conditions are incompatible with these,
    -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
    -
    -  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
    -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
    -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
    -OTHER PARTIES PROVIDE THE LIBRARY "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
    -LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
    -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
    -
    -  16. 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 LIBRARY 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
    -LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
    -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
    -DAMAGES.
    -
    -		     END OF TERMS AND CONDITIONS
    -
    -           How to Apply These Terms to Your New Libraries
    -
    -  If you develop a new library, and you want it to be of the greatest
    -possible use to the public, we recommend making it free software that
    -everyone can redistribute and change.  You can do so by permitting
    -redistribution under these terms (or, alternatively, under the terms of the
    -ordinary General Public License).
    -
    -  To apply these terms, attach the following notices to the library.  It is
    -safest to attach them to the start of each source file to most effectively
    -convey the exclusion of warranty; and each file should have at least the
    -"copyright" line and a pointer to where the full notice is found.
    -
    -    <one line to give the library's name and a brief idea of what it does.>
    -    Copyright (C) <year>  <name of author>
    -
    -    This library is free software; you can redistribute it and/or
    -    modify it under the terms of the GNU Lesser General Public
    -    License as published by the Free Software Foundation; either
    -    version 2.1 of the License, or (at your option) any later version.
    -
    -    This library is distributed in the hope that it will be useful,
    -    but WITHOUT ANY WARRANTY; without even the implied warranty of
    -    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    -    Lesser General Public License for more details.
    -
    -    You should have received a copy of the GNU Lesser General Public
    -    License along with this library; if not, write to the Free Software
    -    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
    -
    -Also add information on how to contact you by electronic and paper mail.
    -
    -You should also get your employer (if you work as a programmer) or your
    -school, if any, to sign a "copyright disclaimer" for the library, if
    -necessary.  Here is a sample; alter the names:
    -
    -  Yoyodyne, Inc., hereby disclaims all copyright interest in the
    -  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
    -
    -  <signature of Ty Coon>, 1 April 1990
    -  Ty Coon, President of Vice
    -
    -That's all there is to it!
    -
    -
    -
    -
    -

    - Documentation generated on Sun, 02 Dec 2007 09:43:23 +0200 by phpDocumentor 1.4.0a2 -

    - - \ No newline at end of file + + + + + + + + + + +

    LICENSE

    +
    +                  GNU LESSER GENERAL PUBLIC LICENSE
    +                       Version 2.1, February 1999
    +
    + Copyright (C) 1991, 1999 Free Software Foundation, Inc.
    +     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
    + Everyone is permitted to copy and distribute verbatim copies
    + of this license document, but changing it is not allowed.
    +
    +[This is the first released version of the Lesser GPL.  It also counts
    + as the successor of the GNU Library Public License, version 2, hence
    + the version number 2.1.]
    +
    +                            Preamble
    +
    +  The licenses for most software are designed to take away your
    +freedom to share and change it.  By contrast, the GNU General Public
    +Licenses are intended to guarantee your freedom to share and change
    +free software--to make sure the software is free for all its users.
    +
    +  This license, the Lesser General Public License, applies to some
    +specially designated software packages--typically libraries--of the
    +Free Software Foundation and other authors who decide to use it.  You
    +can use it too, but we suggest you first think carefully about whether
    +this license or the ordinary General Public License is the better
    +strategy to use in any particular case, based on the explanations below.
    +
    +  When we speak of free software, we are referring to freedom of use,
    +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 and use pieces of
    +it in new free programs; and that you are informed that you can do
    +these things.
    +
    +  To protect your rights, we need to make restrictions that forbid
    +distributors to deny you these rights or to ask you to surrender these
    +rights.  These restrictions translate to certain responsibilities for
    +you if you distribute copies of the library or if you modify it.
    +
    +  For example, if you distribute copies of the library, whether gratis
    +or for a fee, you must give the recipients all the rights that we gave
    +you.  You must make sure that they, too, receive or can get the source
    +code.  If you link other code with the library, you must provide
    +complete object files to the recipients, so that they can relink them
    +with the library after making changes to the library and recompiling
    +it.  And you must show them these terms so they know their rights.
    +
    +  We protect your rights with a two-step method: (1) we copyright the
    +library, and (2) we offer you this license, which gives you legal
    +permission to copy, distribute and/or modify the library.
    +
    +  To protect each distributor, we want to make it very clear that
    +there is no warranty for the free library.  Also, if the library is
    +modified by someone else and passed on, the recipients should know
    +that what they have is not the original version, so that the original
    +author's reputation will not be affected by problems that might be
    +introduced by others.
    +
    +  Finally, software patents pose a constant threat to the existence of
    +any free program.  We wish to make sure that a company cannot
    +effectively restrict the users of a free program by obtaining a
    +restrictive license from a patent holder.  Therefore, we insist that
    +any patent license obtained for a version of the library must be
    +consistent with the full freedom of use specified in this license.
    +
    +  Most GNU software, including some libraries, is covered by the
    +ordinary GNU General Public License.  This license, the GNU Lesser
    +General Public License, applies to certain designated libraries, and
    +is quite different from the ordinary General Public License.  We use
    +this license for certain libraries in order to permit linking those
    +libraries into non-free programs.
    +
    +  When a program is linked with a library, whether statically or using
    +a shared library, the combination of the two is legally speaking a
    +combined work, a derivative of the original library.  The ordinary
    +General Public License therefore permits such linking only if the
    +entire combination fits its criteria of freedom.  The Lesser General
    +Public License permits more lax criteria for linking other code with
    +the library.
    +
    +  We call this license the "Lesser" General Public License because it
    +does Less to protect the user's freedom than the ordinary General
    +Public License.  It also provides other free software developers Less
    +of an advantage over competing non-free programs.  These disadvantages
    +are the reason we use the ordinary General Public License for many
    +libraries.  However, the Lesser license provides advantages in certain
    +special circumstances.
    +
    +  For example, on rare occasions, there may be a special need to
    +encourage the widest possible use of a certain library, so that it becomes
    +a de-facto standard.  To achieve this, non-free programs must be
    +allowed to use the library.  A more frequent case is that a free
    +library does the same job as widely used non-free libraries.  In this
    +case, there is little to gain by limiting the free library to free
    +software only, so we use the Lesser General Public License.
    +
    +  In other cases, permission to use a particular library in non-free
    +programs enables a greater number of people to use a large body of
    +free software.  For example, permission to use the GNU C Library in
    +non-free programs enables many more people to use the whole GNU
    +operating system, as well as its variant, the GNU/Linux operating
    +system.
    +
    +  Although the Lesser General Public License is Less protective of the
    +users' freedom, it does ensure that the user of a program that is
    +linked with the Library has the freedom and the wherewithal to run
    +that program using a modified version of the Library.
    +
    +  The precise terms and conditions for copying, distribution and
    +modification follow.  Pay close attention to the difference between a
    +"work based on the library" and a "work that uses the library".  The
    +former contains code derived from the library, whereas the latter must
    +be combined with the library in order to run.
    +
    +                  GNU LESSER GENERAL PUBLIC LICENSE
    +   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
    +
    +  0. This License Agreement applies to any software library or other
    +program which contains a notice placed by the copyright holder or
    +other authorized party saying it may be distributed under the terms of
    +this Lesser General Public License (also called "this License").
    +Each licensee is addressed as "you".
    +
    +  A "library" means a collection of software functions and/or data
    +prepared so as to be conveniently linked with application programs
    +(which use some of those functions and data) to form executables.
    +
    +  The "Library", below, refers to any such software library or work
    +which has been distributed under these terms.  A "work based on the
    +Library" means either the Library or any derivative work under
    +copyright law: that is to say, a work containing the Library or a
    +portion of it, either verbatim or with modifications and/or translated
    +straightforwardly into another language.  (Hereinafter, translation is
    +included without limitation in the term "modification".)
    +
    +  "Source code" for a work means the preferred form of the work for
    +making modifications to it.  For a library, 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 library.
    +
    +  Activities other than copying, distribution and modification are not
    +covered by this License; they are outside its scope.  The act of
    +running a program using the Library is not restricted, and output from
    +such a program is covered only if its contents constitute a work based
    +on the Library (independent of the use of the Library in a tool for
    +writing it).  Whether that is true depends on what the Library does
    +and what the program that uses the Library does.
    +
    +  1. You may copy and distribute verbatim copies of the Library's
    +complete 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 distribute a copy of this License along with the
    +Library.
    +
    +  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 Library or any portion
    +of it, thus forming a work based on the Library, 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) The modified work must itself be a software library.
    +
    +    b) You must cause the files modified to carry prominent notices
    +    stating that you changed the files and the date of any change.
    +
    +    c) You must cause the whole of the work to be licensed at no
    +    charge to all third parties under the terms of this License.
    +
    +    d) If a facility in the modified Library refers to a function or a
    +    table of data to be supplied by an application program that uses
    +    the facility, other than as an argument passed when the facility
    +    is invoked, then you must make a good faith effort to ensure that,
    +    in the event an application does not supply such function or
    +    table, the facility still operates, and performs whatever part of
    +    its purpose remains meaningful.
    +
    +    (For example, a function in a library to compute square roots has
    +    a purpose that is entirely well-defined independent of the
    +    application.  Therefore, Subsection 2d requires that any
    +    application-supplied function or table used by this function must
    +    be optional: if the application does not supply it, the square
    +    root function must still compute square roots.)
    +
    +These requirements apply to the modified work as a whole.  If
    +identifiable sections of that work are not derived from the Library,
    +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 Library, 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 Library.
    +
    +In addition, mere aggregation of another work not based on the Library
    +with the Library (or with a work based on the Library) on a volume of
    +a storage or distribution medium does not bring the other work under
    +the scope of this License.
    +
    +  3. You may opt to apply the terms of the ordinary GNU General Public
    +License instead of this License to a given copy of the Library.  To do
    +this, you must alter all the notices that refer to this License, so
    +that they refer to the ordinary GNU General Public License, version 2,
    +instead of to this License.  (If a newer version than version 2 of the
    +ordinary GNU General Public License has appeared, then you can specify
    +that version instead if you wish.)  Do not make any other change in
    +these notices.
    +
    +  Once this change is made in a given copy, it is irreversible for
    +that copy, so the ordinary GNU General Public License applies to all
    +subsequent copies and derivative works made from that copy.
    +
    +  This option is useful when you wish to copy part of the code of
    +the Library into a program that is not a library.
    +
    +  4. You may copy and distribute the Library (or a portion or
    +derivative of it, under Section 2) in object code or executable form
    +under the terms of Sections 1 and 2 above provided that you 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.
    +
    +  If distribution of 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 satisfies the requirement to
    +distribute the source code, even though third parties are not
    +compelled to copy the source along with the object code.
    +
    +  5. A program that contains no derivative of any portion of the
    +Library, but is designed to work with the Library by being compiled or
    +linked with it, is called a "work that uses the Library".  Such a
    +work, in isolation, is not a derivative work of the Library, and
    +therefore falls outside the scope of this License.
    +
    +  However, linking a "work that uses the Library" with the Library
    +creates an executable that is a derivative of the Library (because it
    +contains portions of the Library), rather than a "work that uses the
    +library".  The executable is therefore covered by this License.
    +Section 6 states terms for distribution of such executables.
    +
    +  When a "work that uses the Library" uses material from a header file
    +that is part of the Library, the object code for the work may be a
    +derivative work of the Library even though the source code is not.
    +Whether this is true is especially significant if the work can be
    +linked without the Library, or if the work is itself a library.  The
    +threshold for this to be true is not precisely defined by law.
    +
    +  If such an object file uses only numerical parameters, data
    +structure layouts and accessors, and small macros and small inline
    +functions (ten lines or less in length), then the use of the object
    +file is unrestricted, regardless of whether it is legally a derivative
    +work.  (Executables containing this object code plus portions of the
    +Library will still fall under Section 6.)
    +
    +  Otherwise, if the work is a derivative of the Library, you may
    +distribute the object code for the work under the terms of Section 6.
    +Any executables containing that work also fall under Section 6,
    +whether or not they are linked directly with the Library itself.
    +
    +  6. As an exception to the Sections above, you may also combine or
    +link a "work that uses the Library" with the Library to produce a
    +work containing portions of the Library, and distribute that work
    +under terms of your choice, provided that the terms permit
    +modification of the work for the customer's own use and reverse
    +engineering for debugging such modifications.
    +
    +  You must give prominent notice with each copy of the work that the
    +Library is used in it and that the Library and its use are covered by
    +this License.  You must supply a copy of this License.  If the work
    +during execution displays copyright notices, you must include the
    +copyright notice for the Library among them, as well as a reference
    +directing the user to the copy of this License.  Also, you must do one
    +of these things:
    +
    +    a) Accompany the work with the complete corresponding
    +    machine-readable source code for the Library including whatever
    +    changes were used in the work (which must be distributed under
    +    Sections 1 and 2 above); and, if the work is an executable linked
    +    with the Library, with the complete machine-readable "work that
    +    uses the Library", as object code and/or source code, so that the
    +    user can modify the Library and then relink to produce a modified
    +    executable containing the modified Library.  (It is understood
    +    that the user who changes the contents of definitions files in the
    +    Library will not necessarily be able to recompile the application
    +    to use the modified definitions.)
    +
    +    b) Use a suitable shared library mechanism for linking with the
    +    Library.  A suitable mechanism is one that (1) uses at run time a
    +    copy of the library already present on the user's computer system,
    +    rather than copying library functions into the executable, and (2)
    +    will operate properly with a modified version of the library, if
    +    the user installs one, as long as the modified version is
    +    interface-compatible with the version that the work was made with.
    +
    +    c) Accompany the work with a written offer, valid for at
    +    least three years, to give the same user the materials
    +    specified in Subsection 6a, above, for a charge no more
    +    than the cost of performing this distribution.
    +
    +    d) If distribution of the work is made by offering access to copy
    +    from a designated place, offer equivalent access to copy the above
    +    specified materials from the same place.
    +
    +    e) Verify that the user has already received a copy of these
    +    materials or that you have already sent this user a copy.
    +
    +  For an executable, the required form of the "work that uses the
    +Library" must include any data and utility programs needed for
    +reproducing the executable from it.  However, as a special exception,
    +the materials to be 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.
    +
    +  It may happen that this requirement contradicts the license
    +restrictions of other proprietary libraries that do not normally
    +accompany the operating system.  Such a contradiction means you cannot
    +use both them and the Library together in an executable that you
    +distribute.
    +
    +  7. You may place library facilities that are a work based on the
    +Library side-by-side in a single library together with other library
    +facilities not covered by this License, and distribute such a combined
    +library, provided that the separate distribution of the work based on
    +the Library and of the other library facilities is otherwise
    +permitted, and provided that you do these two things:
    +
    +    a) Accompany the combined library with a copy of the same work
    +    based on the Library, uncombined with any other library
    +    facilities.  This must be distributed under the terms of the
    +    Sections above.
    +
    +    b) Give prominent notice with the combined library of the fact
    +    that part of it is a work based on the Library, and explaining
    +    where to find the accompanying uncombined form of the same work.
    +
    +  8. You may not copy, modify, sublicense, link with, or distribute
    +the Library except as expressly provided under this License.  Any
    +attempt otherwise to copy, modify, sublicense, link with, or
    +distribute the Library 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.
    +
    +  9. 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 Library or its derivative works.  These actions are
    +prohibited by law if you do not accept this License.  Therefore, by
    +modifying or distributing the Library (or any work based on the
    +Library), you indicate your acceptance of this License to do so, and
    +all its terms and conditions for copying, distributing or modifying
    +the Library or works based on it.
    +
    +  10. Each time you redistribute the Library (or any work based on the
    +Library), the recipient automatically receives a license from the
    +original licensor to copy, distribute, link with or modify the Library
    +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 with
    +this License.
    +
    +  11. 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 Library at all.  For example, if a patent
    +license would not permit royalty-free redistribution of the Library 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 Library.
    +
    +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.
    +
    +  12. If the distribution and/or use of the Library is restricted in
    +certain countries either by patents or by copyrighted interfaces, the
    +original copyright holder who places the Library 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.
    +
    +  13. The Free Software Foundation may publish revised and/or new
    +versions of the Lesser 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 Library
    +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 Library does not specify a
    +license version number, you may choose any version ever published by
    +the Free Software Foundation.
    +
    +  14. If you wish to incorporate parts of the Library into other free
    +programs whose distribution conditions are incompatible with these,
    +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
    +
    +  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
    +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
    +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
    +OTHER PARTIES PROVIDE THE LIBRARY "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
    +LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
    +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
    +
    +  16. 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 LIBRARY 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
    +LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
    +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
    +DAMAGES.
    +
    +                     END OF TERMS AND CONDITIONS
    +
    +           How to Apply These Terms to Your New Libraries
    +
    +  If you develop a new library, and you want it to be of the greatest
    +possible use to the public, we recommend making it free software that
    +everyone can redistribute and change.  You can do so by permitting
    +redistribution under these terms (or, alternatively, under the terms of the
    +ordinary General Public License).
    +
    +  To apply these terms, attach the following notices to the library.  It is
    +safest to attach them to the start of each source file to most effectively
    +convey the exclusion of warranty; and each file should have at least the
    +"copyright" line and a pointer to where the full notice is found.
    +
    +    <one line to give the library's name and a brief idea of what it does.>
    +    Copyright (C) <year>  <name of author>
    +
    +    This library is free software; you can redistribute it and/or
    +    modify it under the terms of the GNU Lesser General Public
    +    License as published by the Free Software Foundation; either
    +    version 2.1 of the License, or (at your option) any later version.
    +
    +    This library is distributed in the hope that it will be useful,
    +    but WITHOUT ANY WARRANTY; without even the implied warranty of
    +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    +    Lesser General Public License for more details.
    +
    +    You should have received a copy of the GNU Lesser General Public
    +    License along with this library; if not, write to the Free Software
    +    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
    +
    +Also add information on how to contact you by electronic and paper mail.
    +
    +You should also get your employer (if you work as a programmer) or your
    +school, if any, to sign a "copyright disclaimer" for the library, if
    +necessary.  Here is a sample; alter the names:
    +
    +  Yoyodyne, Inc., hereby disclaims all copyright interest in the
    +  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
    +
    +  <signature of Ty Coon>, 1 April 1990
    +  Ty Coon, President of Vice
    +
    +That's all there is to it!
    +
    +
    +
    +
    +

    + Documentation generated on Sun, 02 Dec 2007 09:43:23 +0200 by phpDocumentor 1.4.0a2 +

    + + diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/ric_README.html b/sites/all/modules/contrib/dev/devel/krumo/docs/ric_README.html old mode 100755 new mode 100644 index 02a45c8..49f5262 --- a/sites/all/modules/contrib/dev/devel/krumo/docs/ric_README.html +++ b/sites/all/modules/contrib/dev/devel/krumo/docs/ric_README.html @@ -1,121 +1,122 @@ - - - - - - - - - -

    README

    -
    -=============================================================================
    -
    -                               Krumo
    -                            version 0.2.1a
    -
    -=============================================================================
    -
    -You probably got this package from...
    -http://www.sourceforge.net/projects/krumo/
    -
    -If there is no licence agreement with this package please download
    -a version from the location above. You must read and accept that
    -licence to use this software. The file is titled simply LICENSE.
    -
    -OVERVIEW
    -------------------------------------------------------------------------------
    -To put it simply, Krumo is a replacement for print_r() and var_dump(). By 
    -definition Krumo is a debugging tool (for PHP5), which displays structured 
    -information about any PHP variable.
    -
    -A lot of developers use print_r() and var_dump() in the means of debugging 
    -tools. Although they were intended to present human readble information about a 
    -variable, we can all agree that in general they are not. Krumo is an 
    -alternative: it does the same job, but it presents the information beautified 
    -using CSS and DHTML. 
    -
    -EXAMPLES
    -------------------------------------------------------------------------------
    -Here's a basic example, which will return a report on the array variable passed 
    -as argument to it:
    -
    - krumo(array('a1'=> 'A1', 3, 'red'));
    -
    -You can dump simultaneously more then one variable - here's another example:
    -
    - krumo($_SERVER, $_REQUEST);
    -
    -You probably saw from the examples above that some of the nodes are expandable, 
    -so if you want to inspect the nested information, click on them and they will 
    -expand; if you do not need that information shown simply click again on it to 
    -collapse it. Here's an example to test this:
    -
    - $x1->x2->x3->x4->x5->x6->x7->x8->x9 = 'X10';
    - krumo($x1);
    -
    -The krumo() is the only standalone function from the package, and this is 
    -because basic dumps about variables (like print_r() or var_dump()) are the most 
    -common tasks such functionality is used for. The rest of the functionality can 
    -be called using static calls to the Krumo class. Here are several more examples:
    -
    - // print a debug backgrace
    - krumo::backtrace();
    -
    - // print all the included(or required) files
    - krumo::includes();
    - 
    - // print all the included functions
    - krumo::functions();
    - 
    - // print all the declared classes
    - krumo::classes();
    - 
    - // print all the defined constants
    - krumo::defines();
    -
    - ... and so on, etc.
    -
    -A full PHPDocumenter API documentation exists both in this package and at the 
    -project's website.
    -
    -INSTALL
    -------------------------------------------------------------------------------
    -Read the INSTALL file.
    -
    -DOCUMENTATION
    -------------------------------------------------------------------------------
    -As I said, a full PHPDocumenter API documentation can be found both in this
    -package and at the project's website.
    -
    -SKINS
    -------------------------------------------------------------------------------
    -There are several skins pre-installed with this package, but if you wish you can 
    -create skins of your own. The skins are simply CSS files that are prepended to 
    -the result that Krumo prints. If you want to use images in your CSS (for 
    -background, list-style, etc), you have to put "%URL%" in front of the image URL 
    -in order hook it up to the skin folder and make the image web-accessible.
    -
    -Here's an example:
    -
    - ul.krumo-first {background: url(%url%bg.gif);}
    -
    -TODO
    -------------------------------------------------------------------------------
    -You can find the list of stuff that is going to be added to this project in the 
    -TODO file from this very package.
    -
    -CONTRIBUTION
    ------------------------------------------------------------------------------
    -If you download and use and possibly even extend this tool, please let us know. 
    -Any feedback, even bad, is always welcome and your suggestions are going to be 
    -considered for our next release. Please use our SourceForge page for that:
    - 
    - http://www.sourceforge.net/projects/krumo/
    -
    -
    -

    - Documentation generated on Sun, 02 Dec 2007 09:43:23 +0200 by phpDocumentor 1.4.0a2 -

    - - \ No newline at end of file + + + + + + + + + + +

    README

    +
    +=============================================================================
    +
    +                               Krumo
    +                            version 0.2.1a
    +
    +=============================================================================
    +
    +You probably got this package from...
    +http://www.sourceforge.net/projects/krumo/
    +
    +If there is no licence agreement with this package please download
    +a version from the location above. You must read and accept that
    +licence to use this software. The file is titled simply LICENSE.
    +
    +OVERVIEW
    +------------------------------------------------------------------------------
    +To put it simply, Krumo is a replacement for print_r() and var_dump(). By
    +definition Krumo is a debugging tool (for PHP5), which displays structured
    +information about any PHP variable.
    +
    +A lot of developers use print_r() and var_dump() in the means of debugging
    +tools. Although they were intended to present human readble information about a
    +variable, we can all agree that in general they are not. Krumo is an
    +alternative: it does the same job, but it presents the information beautified
    +using CSS and DHTML.
    +
    +EXAMPLES
    +------------------------------------------------------------------------------
    +Here's a basic example, which will return a report on the array variable passed
    +as argument to it:
    +
    + krumo(array('a1'=> 'A1', 3, 'red'));
    +
    +You can dump simultaneously more then one variable - here's another example:
    +
    + krumo($_SERVER, $_REQUEST);
    +
    +You probably saw from the examples above that some of the nodes are expandable,
    +so if you want to inspect the nested information, click on them and they will
    +expand; if you do not need that information shown simply click again on it to
    +collapse it. Here's an example to test this:
    +
    + $x1->x2->x3->x4->x5->x6->x7->x8->x9 = 'X10';
    + krumo($x1);
    +
    +The krumo() is the only standalone function from the package, and this is
    +because basic dumps about variables (like print_r() or var_dump()) are the most
    +common tasks such functionality is used for. The rest of the functionality can
    +be called using static calls to the Krumo class. Here are several more examples:
    +
    + // print a debug backgrace
    + krumo::backtrace();
    +
    + // print all the included(or required) files
    + krumo::includes();
    +
    + // print all the included functions
    + krumo::functions();
    +
    + // print all the declared classes
    + krumo::classes();
    +
    + // print all the defined constants
    + krumo::defines();
    +
    + ... and so on, etc.
    +
    +A full PHPDocumenter API documentation exists both in this package and at the
    +project's website.
    +
    +INSTALL
    +------------------------------------------------------------------------------
    +Read the INSTALL file.
    +
    +DOCUMENTATION
    +------------------------------------------------------------------------------
    +As I said, a full PHPDocumenter API documentation can be found both in this
    +package and at the project's website.
    +
    +SKINS
    +------------------------------------------------------------------------------
    +There are several skins pre-installed with this package, but if you wish you can
    +create skins of your own. The skins are simply CSS files that are prepended to
    +the result that Krumo prints. If you want to use images in your CSS (for
    +background, list-style, etc), you have to put "%URL%" in front of the image URL
    +in order hook it up to the skin folder and make the image web-accessible.
    +
    +Here's an example:
    +
    + ul.krumo-first {background: url(%url%bg.gif);}
    +
    +TODO
    +------------------------------------------------------------------------------
    +You can find the list of stuff that is going to be added to this project in the
    +TODO file from this very package.
    +
    +CONTRIBUTION
    +-----------------------------------------------------------------------------
    +If you download and use and possibly even extend this tool, please let us know.
    +Any feedback, even bad, is always welcome and your suggestions are going to be
    +considered for our next release. Please use our SourceForge page for that:
    +
    + http://www.sourceforge.net/projects/krumo/
    +
    +
    +

    + Documentation generated on Sun, 02 Dec 2007 09:43:23 +0200 by phpDocumentor 1.4.0a2 +

    + + diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/ric_TODO.html b/sites/all/modules/contrib/dev/devel/krumo/docs/ric_TODO.html old mode 100755 new mode 100644 index 82b751e..b0eba4d --- a/sites/all/modules/contrib/dev/devel/krumo/docs/ric_TODO.html +++ b/sites/all/modules/contrib/dev/devel/krumo/docs/ric_TODO.html @@ -1,41 +1,42 @@ - - - - - - - - - -

    TODO

    -
    -******************************************************************************
    -
    -                                 Krumo: TODO
    -
    -******************************************************************************
    -
    -BUGS
    -----------------
    - - watch the SourceForge.net Bug Tracker
    -
    -Features: PHP
    -----------------
    - - Try to detect anonymous (lambda) functions
    - - Try to detect whether an array is indexed or associated
    - - Add var_export support for arrays and objects
    - - Add JSON support for arrays and objects
    - 
    -Features: GUI
    -----------------
    - - Nicer and friendlier skin(s)
    - - Add top-level links for collapsing and expanding the whole tree
    - - Add object & array -level links for collapsing and expanding all the
    - 	nested nodes
    - - Print all parent classes for the rendered objects
    -
    -

    - Documentation generated on Sun, 02 Dec 2007 09:43:23 +0200 by phpDocumentor 1.4.0a2 -

    - - \ No newline at end of file + + + + + + + + + + +

    TODO

    +
    +******************************************************************************
    +
    +                                 Krumo: TODO
    +
    +******************************************************************************
    +
    +BUGS
    +----------------
    + - watch the SourceForge.net Bug Tracker
    +
    +Features: PHP
    +----------------
    + - Try to detect anonymous (lambda) functions
    + - Try to detect whether an array is indexed or associated
    + - Add var_export support for arrays and objects
    + - Add JSON support for arrays and objects
    +
    +Features: GUI
    +----------------
    + - Nicer and friendlier skin(s)
    + - Add top-level links for collapsing and expanding the whole tree
    + - Add object & array -level links for collapsing and expanding all the
    +   nested nodes
    + - Print all parent classes for the rendered objects
    +
    +

    + Documentation generated on Sun, 02 Dec 2007 09:43:23 +0200 by phpDocumentor 1.4.0a2 +

    + + diff --git a/sites/all/modules/contrib/dev/devel/krumo/docs/ric_VERSION.html b/sites/all/modules/contrib/dev/devel/krumo/docs/ric_VERSION.html old mode 100755 new mode 100644 index 4590404..f929fc8 --- a/sites/all/modules/contrib/dev/devel/krumo/docs/ric_VERSION.html +++ b/sites/all/modules/contrib/dev/devel/krumo/docs/ric_VERSION.html @@ -1,18 +1,19 @@ - - - - - - - - - -

    VERSION

    -
    -0.2.1a
    -
    -

    - Documentation generated on Sun, 02 Dec 2007 09:43:23 +0200 by phpDocumentor 1.4.0a2 -

    - - \ No newline at end of file + + + + + + + + + + +

    VERSION

    +
    +0.2.1a
    +
    +

    + Documentation generated on Sun, 02 Dec 2007 09:43:23 +0200 by phpDocumentor 1.4.0a2 +

    + + diff --git a/sites/all/modules/contrib/dev/devel/krumo/krumo.ini b/sites/all/modules/contrib/dev/devel/krumo/krumo.ini old mode 100755 new mode 100644 index 3e20dd9..8946ec0 --- a/sites/all/modules/contrib/dev/devel/krumo/krumo.ini +++ b/sites/all/modules/contrib/dev/devel/krumo/krumo.ini @@ -1,20 +1,20 @@ -; -; KRUMO CONFIGURATION FILE -; - -[skin] -selected = "white" -; -; Change the above value to set the CSS skin used to render -; Krumo layout. If the skin is not found, then the "default" one -; is going to be used. -; - -[css] -url = "http://www.example.com/Krumo/" -; -; This value is used to set the URL path to -; where the Krumo folder is. This is required in -; order to have web access to Krumo's CSS and -; image files. -; +; +; KRUMO CONFIGURATION FILE +; + +[skin] +selected = "white" +; +; Change the above value to set the CSS skin used to render +; Krumo layout. If the skin is not found, then the "default" one +; is going to be used. +; + +[css] +url = "http://www.example.com/Krumo/" +; +; This value is used to set the URL path to +; where the Krumo folder is. This is required in +; order to have web access to Krumo's CSS and +; image files. +; diff --git a/sites/all/modules/contrib/dev/devel/krumo/krumo.js b/sites/all/modules/contrib/dev/devel/krumo/krumo.js old mode 100755 new mode 100644 index 240af8c..10631ee --- a/sites/all/modules/contrib/dev/devel/krumo/krumo.js +++ b/sites/all/modules/contrib/dev/devel/krumo/krumo.js @@ -1,97 +1,97 @@ -/** -* JavaScript routines for Krumo -* -* @link http://sourceforge.net/projects/krumo -*/ - -///////////////////////////////////////////////////////////////////////////// - -/** -* Krumo JS Class -*/ -function krumo() { - } - -// -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - -/** -* Add a CSS class to an HTML element -* -* @param HtmlElement el -* @param string className -* @return void -*/ -krumo.reclass = function(el, className) { - if (el.className.indexOf(className) < 0) { - el.className += (' ' + className); - } - } - -// -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - -/** -* Remove a CSS class to an HTML element -* -* @param HtmlElement el -* @param string className -* @return void -*/ -krumo.unclass = function(el, className) { - if (el.className.indexOf(className) > -1) { - el.className = el.className.replace(className, ''); - } - } - -// -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - -/** -* Toggle the nodes connected to an HTML element -* -* @param HtmlElement el -* @return void -*/ -krumo.toggle = function(el) { - var ul = el.parentNode.getElementsByTagName('ul'); - for (var i=0; i -1) { + el.className = el.className.replace(className, ''); + } + } + +// -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- + +/** +* Toggle the nodes connected to an HTML element +* +* @param HtmlElement el +* @return void +*/ +krumo.toggle = function(el) { + var ul = el.parentNode.getElementsByTagName('ul'); + for (var i=0; i -*/ - -/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ - -ul.krumo-node { - margin: 0px; - padding: 0px; - } -ul.krumo-node ul { - margin-left: 20px; - } -* html ul.krumo-node ul { - margin-left: 24px; - } -div.krumo-root { - border: solid 1px black; - margin: 1em 0em; - } -ul.krumo-first { - font: normal 12px arial; - border: solid 2px white; - border-top-width:1px; - background: url(%url%bg.gif); - } - -/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ - -li.krumo-child { - display:block; - list-style: none; - padding: 0px; - margin: 0px; - overflow:hidden; - } -div.krumo-element { - cursor:default; - - line-height: 24px; - display:block; - - clear:both; - white-space:nowrap; - - border-top: solid 1px white; - background: #BFDFFF; - padding-left: 10px; - } -* html div.krumo-element { - padding-bottom: 3px; - } -a.krumo-name { - color:navy; - font: bold 13px Arial; - } -a.krumo-name big { - font: bold 20pt Georgia; - line-height: 14px; - position:relative; - top:2px; - left:-2px; - } -* html a.krumo-name big { - font: bold 19pt Georgia; - top: 5px; - left: 0px; - line-height: 9px; - height: 12px; - padding: 0px; - margin: 0px; - } -div.krumo-expand { - background: #AAD5FF; - cursor:pointer; - } -div.krumo-hover { - background: #FFBE7D; - } - -/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ - -div.krumo-preview { - font: normal 13px courier new; - padding: 5px 5px 14px 5px; - background: white; - border-top: 0px; - overflow:auto; - white-space: pre; - } -* html div.krumo-preview { - padding-top: 2px; - } - -/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ - -li.krumo-footnote { - background:white; - padding: 2px 5px; - list-style:none; - border-top: solid 1px #bebebe; - margin-top:2px; - cursor:default; - } -* html li.krumo-footnote { - line-height: 13px; - } -div.krumo-version { - float:right; - } -li.krumo-footnote h6 { - font: bold 11px verdana; - margin: 0px; - padding: 0px; - color:navy; - display:inline; - } -* html li.krumo-footnote h6 { - margin-right: 3px; - } -li.krumo-footnote a { - font: bold 10px arial; - color: #434343; - text-decoration:none; - } -li.krumo-footnote a:hover { - color:black; - } - -li.krumo-footnote span.krumo-call { - font:normal 11px verdana; - position: relative; - top: 1px; - } -li.krumo-footnote span.krumo-call code { - font-weight:bold; - } - -/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ - -div.krumo-title { - font: normal 11px verdana ; - position:relative; - top:9px; - cursor:default; - line-height:2px; - } - -/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ - -strong.krumo-array-length, -strong.krumo-string-length { - font-weight: normal; - } - -/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ +/** +* Krumo "Blue" Skin +* +* @author Kaloyan K. Tsvetkov +*/ + +/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ + +ul.krumo-node { + margin: 0px; + padding: 0px; + } +ul.krumo-node ul { + margin-left: 20px; + } +* html ul.krumo-node ul { + margin-left: 24px; + } +div.krumo-root { + border: solid 1px black; + margin: 1em 0em; + } +ul.krumo-first { + font: normal 12px arial; + border: solid 2px white; + border-top-width:1px; + background: url(%url%bg.gif); + } + +/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ + +li.krumo-child { + display:block; + list-style: none; + padding: 0px; + margin: 0px; + overflow:hidden; + } +div.krumo-element { + cursor:default; + + line-height: 24px; + display:block; + + clear:both; + white-space:nowrap; + + border-top: solid 1px white; + background: #BFDFFF; + padding-left: 10px; + } +* html div.krumo-element { + padding-bottom: 3px; + } +a.krumo-name { + color:navy; + font: bold 13px Arial; + } +a.krumo-name span.krumo-big { + font: bold 20pt Georgia; + line-height: 14px; + position:relative; + top:2px; + left:-2px; + } +* html a.krumo-name span.krumo-big { + font: bold 19pt Georgia; + top: 5px; + left: 0px; + line-height: 9px; + height: 12px; + padding: 0px; + margin: 0px; + } +div.krumo-expand { + background: #AAD5FF; + cursor:pointer; + } +div.krumo-hover { + background: #FFBE7D; + } + +/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ + +div.krumo-preview { + font: normal 13px courier new; + padding: 5px 5px 14px 5px; + background: white; + border-top: 0px; + overflow:auto; + white-space: pre; + } +* html div.krumo-preview { + padding-top: 2px; + } + +/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ + +li.krumo-footnote { + background:white; + padding: 2px 5px; + list-style:none; + border-top: solid 1px #bebebe; + margin-top:2px; + cursor:default; + } +* html li.krumo-footnote { + line-height: 13px; + } +div.krumo-version { + float:right; + } +li.krumo-footnote h6 { + font: bold 11px verdana; + margin: 0px; + padding: 0px; + color:navy; + display:inline; + } +* html li.krumo-footnote h6 { + margin-right: 3px; + } +li.krumo-footnote a { + font: bold 10px arial; + color: #434343; + text-decoration:none; + } +li.krumo-footnote a:hover { + color:black; + } + +li.krumo-footnote span.krumo-call { + font:normal 11px verdana; + position: relative; + top: 1px; + } +li.krumo-footnote span.krumo-call code { + font-weight:bold; + } + +/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ + +div.krumo-title { + font: normal 11px verdana ; + position:relative; + top:9px; + cursor:default; + line-height:2px; + } + +/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ + +strong.krumo-array-length, +strong.krumo-string-length { + font-weight: normal; + } + +/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ diff --git a/sites/all/modules/contrib/dev/devel/krumo/skins/default/bg.gif b/sites/all/modules/contrib/dev/devel/krumo/skins/default/bg.gif old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/skins/default/skin.css b/sites/all/modules/contrib/dev/devel/krumo/skins/default/skin.css old mode 100755 new mode 100644 index 5c29d70..d18b458 --- a/sites/all/modules/contrib/dev/devel/krumo/skins/default/skin.css +++ b/sites/all/modules/contrib/dev/devel/krumo/skins/default/skin.css @@ -1,158 +1,158 @@ -/** -* Krumo Default Skin -* -* @author Kaloyan K. Tsvetkov -*/ - -/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ - -ul.krumo-node { - margin: 0px; - padding: 0px; - } -ul.krumo-node ul { - margin-left: 20px; - } -* html ul.krumo-node ul { - margin-left: 24px; - } -div.krumo-root { - border: solid 1px black; - margin: 1em 0em; - } -ul.krumo-first { - font: normal 12px arial; - border: solid 2px white; - border-top-width:1px; - background: url(%url%bg.gif); - } - -/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ - -li.krumo-child { - display:block; - list-style: none; - padding: 0px; - margin: 0px; - overflow:hidden; - } -div.krumo-element { - cursor:default; - - line-height: 24px; - display:block; - - clear:both; - white-space:nowrap; - - border-top: solid 1px white; - background: #E8E8E8; - padding-left: 10px; - } -* html div.krumo-element { - padding-bottom: 3px; - } -a.krumo-name { - color:#2C5858; - font: bold 13px Arial; - } -a.krumo-name big { - font: bold 20pt Georgia; - line-height: 14px; - position:relative; - top:2px; - left:-2px; - } -* html a.krumo-name big { - font: bold 19pt Georgia; - top: 5px; - left: 0px; - line-height: 9px; - height: 12px; - padding: 0px; - margin: 0px; - } -div.krumo-expand { - background: #CCCCCC; - cursor:pointer; - } -div.krumo-hover { - background: #B7DBDB; - } - -/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ - -div.krumo-preview { - font: normal 13px courier new; - padding: 5px 5px 14px 5px; - background: white; - border-top: 0px; - overflow:auto; - white-space: pre; - } -* html div.krumo-preview { - padding-top: 2px; - } - -/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ - -li.krumo-footnote { - background:white; - padding: 2px 5px; - list-style:none; - border-top: solid 1px #bebebe; - margin-top:2px; - cursor:default; - } -* html li.krumo-footnote { - line-height: 13px; - } -div.krumo-version { - float:right; - } -li.krumo-footnote h6 { - font: bold 11px verdana; - margin: 0px; - padding: 0px; - color:#366D6D; - display:inline; - } -* html li.krumo-footnote h6 { - margin-right: 3px; - } -li.krumo-footnote a { - font: bold 10px arial; - color: #434343; - text-decoration:none; - } -li.krumo-footnote a:hover { - color:black; - } - -li.krumo-footnote span.krumo-call { - font:normal 11px verdana; - position: relative; - top: 1px; - } -li.krumo-footnote span.krumo-call code { - font-weight:bold; - } - -/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ - -div.krumo-title { - font: normal 11px verdana ; - position:relative; - top:9px; - cursor:default; - line-height:2px; - } - -/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ - -strong.krumo-array-length, -strong.krumo-string-length { - font-weight: normal; - } - -/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ +/** +* Krumo Default Skin +* +* @author Kaloyan K. Tsvetkov +*/ + +/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ + +ul.krumo-node { + margin: 0px; + padding: 0px; + } +ul.krumo-node ul { + margin-left: 20px; + } +* html ul.krumo-node ul { + margin-left: 24px; + } +div.krumo-root { + border: solid 1px black; + margin: 1em 0em; + } +ul.krumo-first { + font: normal 12px arial; + border: solid 2px white; + border-top-width:1px; + background: url(%url%bg.gif); + } + +/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ + +li.krumo-child { + display:block; + list-style: none; + padding: 0px; + margin: 0px; + overflow:hidden; + } +div.krumo-element { + cursor:default; + + line-height: 24px; + display:block; + + clear:both; + white-space:nowrap; + + border-top: solid 1px white; + background: #E8E8E8; + padding-left: 10px; + } +* html div.krumo-element { + padding-bottom: 3px; + } +a.krumo-name { + color:#2C5858; + font: bold 13px Arial; + } +a.krumo-name span.krumo-big { + font: bold 20pt Georgia; + line-height: 14px; + position:relative; + top:2px; + left:-2px; + } +* html a.krumo-name span.krumo-big { + font: bold 19pt Georgia; + top: 5px; + left: 0px; + line-height: 9px; + height: 12px; + padding: 0px; + margin: 0px; + } +div.krumo-expand { + background: #CCCCCC; + cursor:pointer; + } +div.krumo-hover { + background: #B7DBDB; + } + +/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ + +div.krumo-preview { + font: normal 13px courier new; + padding: 5px 5px 14px 5px; + background: white; + border-top: 0px; + overflow:auto; + white-space: pre; + } +* html div.krumo-preview { + padding-top: 2px; + } + +/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ + +li.krumo-footnote { + background:white; + padding: 2px 5px; + list-style:none; + border-top: solid 1px #bebebe; + margin-top:2px; + cursor:default; + } +* html li.krumo-footnote { + line-height: 13px; + } +div.krumo-version { + float:right; + } +li.krumo-footnote h6 { + font: bold 11px verdana; + margin: 0px; + padding: 0px; + color:#366D6D; + display:inline; + } +* html li.krumo-footnote h6 { + margin-right: 3px; + } +li.krumo-footnote a { + font: bold 10px arial; + color: #434343; + text-decoration:none; + } +li.krumo-footnote a:hover { + color:black; + } + +li.krumo-footnote span.krumo-call { + font:normal 11px verdana; + position: relative; + top: 1px; + } +li.krumo-footnote span.krumo-call code { + font-weight:bold; + } + +/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ + +div.krumo-title { + font: normal 11px verdana ; + position:relative; + top:9px; + cursor:default; + line-height:2px; + } + +/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ + +strong.krumo-array-length, +strong.krumo-string-length { + font-weight: normal; + } + +/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ diff --git a/sites/all/modules/contrib/dev/devel/krumo/skins/green/bg.gif b/sites/all/modules/contrib/dev/devel/krumo/skins/green/bg.gif old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/skins/green/skin.css b/sites/all/modules/contrib/dev/devel/krumo/skins/green/skin.css old mode 100755 new mode 100644 index ffaf189..e80006f --- a/sites/all/modules/contrib/dev/devel/krumo/skins/green/skin.css +++ b/sites/all/modules/contrib/dev/devel/krumo/skins/green/skin.css @@ -1,158 +1,158 @@ -/** -* Krumo "Green" Skin -* -* @author Kaloyan K. Tsvetkov -*/ - -/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ - -ul.krumo-node { - margin: 0px; - padding: 0px; - } -ul.krumo-node ul { - margin-left: 20px; - } -* html ul.krumo-node ul { - margin-left: 24px; - } -div.krumo-root { - border: solid 1px black; - margin: 1em 0em; - } -ul.krumo-first { - font: normal 12px arial; - border: solid 2px white; - border-top-width:1px; - background: url(%url%bg.gif); - } - -/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ - -li.krumo-child { - display:block; - list-style: none; - padding: 0px; - margin: 0px; - overflow:hidden; - } -div.krumo-element { - cursor:default; - - line-height: 24px; - display:block; - - clear:both; - white-space:nowrap; - - border-top: solid 1px white; - background: #D7F4CA; - padding-left: 10px; - } -* html div.krumo-element { - padding-bottom: 3px; - } -a.krumo-name { - color:#004000; - font: bold 13px Arial; - } -a.krumo-name big { - font: bold 20pt Georgia; - line-height: 14px; - position:relative; - top:2px; - left:-2px; - } -* html a.krumo-name big { - font: bold 19pt Georgia; - top: 5px; - left: 0px; - line-height: 9px; - height: 12px; - padding: 0px; - margin: 0px; - } -div.krumo-expand { - background: #C0EEAC; - cursor:pointer; - } -div.krumo-hover { - background: gold; - } - -/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ - -div.krumo-preview { - font: normal 13px courier new; - padding: 5px 5px 14px 5px; - background: white; - border-top: 0px; - overflow:auto; - white-space: pre; - } -* html div.krumo-preview { - padding-top: 2px; - } - -/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ - -li.krumo-footnote { - background:white; - padding: 2px 5px; - list-style:none; - border-top: solid 1px #bebebe; - margin-top:2px; - cursor:default; - } -* html li.krumo-footnote { - line-height: 13px; - } -div.krumo-version { - float:right; - } -li.krumo-footnote h6 { - font: bold 11px verdana; - margin: 0px; - padding: 0px; - color:#008040; - display:inline; - } -* html li.krumo-footnote h6 { - margin-right: 3px; - } -li.krumo-footnote a { - font: bold 10px arial; - color: #434343; - text-decoration:none; - } -li.krumo-footnote a:hover { - color:black; - } - -li.krumo-footnote span.krumo-call { - font:normal 11px verdana; - position: relative; - top: 1px; - } -li.krumo-footnote span.krumo-call code { - font-weight:bold; - } - -/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ - -div.krumo-title { - font: normal 11px verdana ; - position:relative; - top:9px; - cursor:default; - line-height:2px; - } - -/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ - -strong.krumo-array-length, -strong.krumo-string-length { - font-weight: normal; - } - -/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ +/** +* Krumo "Green" Skin +* +* @author Kaloyan K. Tsvetkov +*/ + +/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ + +ul.krumo-node { + margin: 0px; + padding: 0px; + } +ul.krumo-node ul { + margin-left: 20px; + } +* html ul.krumo-node ul { + margin-left: 24px; + } +div.krumo-root { + border: solid 1px black; + margin: 1em 0em; + } +ul.krumo-first { + font: normal 12px arial; + border: solid 2px white; + border-top-width:1px; + background: url(%url%bg.gif); + } + +/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ + +li.krumo-child { + display:block; + list-style: none; + padding: 0px; + margin: 0px; + overflow:hidden; + } +div.krumo-element { + cursor:default; + + line-height: 24px; + display:block; + + clear:both; + white-space:nowrap; + + border-top: solid 1px white; + background: #D7F4CA; + padding-left: 10px; + } +* html div.krumo-element { + padding-bottom: 3px; + } +a.krumo-name { + color:#004000; + font: bold 13px Arial; + } +a.krumo-name span.krumo-big { + font: bold 20pt Georgia; + line-height: 14px; + position:relative; + top:2px; + left:-2px; + } +* html a.krumo-name span.krumo-big { + font: bold 19pt Georgia; + top: 5px; + left: 0px; + line-height: 9px; + height: 12px; + padding: 0px; + margin: 0px; + } +div.krumo-expand { + background: #C0EEAC; + cursor:pointer; + } +div.krumo-hover { + background: gold; + } + +/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ + +div.krumo-preview { + font: normal 13px courier new; + padding: 5px 5px 14px 5px; + background: white; + border-top: 0px; + overflow:auto; + white-space: pre; + } +* html div.krumo-preview { + padding-top: 2px; + } + +/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ + +li.krumo-footnote { + background:white; + padding: 2px 5px; + list-style:none; + border-top: solid 1px #bebebe; + margin-top:2px; + cursor:default; + } +* html li.krumo-footnote { + line-height: 13px; + } +div.krumo-version { + float:right; + } +li.krumo-footnote h6 { + font: bold 11px verdana; + margin: 0px; + padding: 0px; + color:#008040; + display:inline; + } +* html li.krumo-footnote h6 { + margin-right: 3px; + } +li.krumo-footnote a { + font: bold 10px arial; + color: #434343; + text-decoration:none; + } +li.krumo-footnote a:hover { + color:black; + } + +li.krumo-footnote span.krumo-call { + font:normal 11px verdana; + position: relative; + top: 1px; + } +li.krumo-footnote span.krumo-call code { + font-weight:bold; + } + +/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ + +div.krumo-title { + font: normal 11px verdana ; + position:relative; + top:9px; + cursor:default; + line-height:2px; + } + +/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ + +strong.krumo-array-length, +strong.krumo-string-length { + font-weight: normal; + } + +/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ diff --git a/sites/all/modules/contrib/dev/devel/krumo/skins/orange/bg.gif b/sites/all/modules/contrib/dev/devel/krumo/skins/orange/bg.gif old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/skins/orange/skin.css b/sites/all/modules/contrib/dev/devel/krumo/skins/orange/skin.css old mode 100755 new mode 100644 index 840680c..cdce857 --- a/sites/all/modules/contrib/dev/devel/krumo/skins/orange/skin.css +++ b/sites/all/modules/contrib/dev/devel/krumo/skins/orange/skin.css @@ -1,158 +1,158 @@ -/** -* Krumo "Orange" Skin -* -* @author Kaloyan K. Tsvetkov -*/ - -/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ - -ul.krumo-node { - margin: 0px; - padding: 0px; - } -ul.krumo-node ul { - margin-left: 20px; - } -* html ul.krumo-node ul { - margin-left: 24px; - } -div.krumo-root { - border: solid 1px black; - margin: 1em 0em; - } -ul.krumo-first { - font: normal 12px arial; - border: solid 2px white; - border-top-width:1px; - background: url(%url%bg.gif); - } - -/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ - -li.krumo-child { - display:block; - list-style: none; - padding: 0px; - margin: 0px; - overflow:hidden; - } -div.krumo-element { - cursor:default; - - line-height: 24px; - display:block; - - clear:both; - white-space:nowrap; - - border-top: solid 1px white; - background: #FCEBA9; - padding-left: 10px; - } -* html div.krumo-element { - padding-bottom: 3px; - } -a.krumo-name { - color:#404000; - font: bold 13px Arial; - } -a.krumo-name big { - font: bold 20pt Georgia; - line-height: 14px; - position:relative; - top:2px; - left:-2px; - } -* html a.krumo-name big { - font: bold 19pt Georgia; - top: 5px; - left: 0px; - line-height: 9px; - height: 12px; - padding: 0px; - margin: 0px; - } -div.krumo-expand { - background: #FADB61; - cursor:pointer; - } -div.krumo-hover { - background: #FF8A4B; - } - -/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ - -div.krumo-preview { - font: normal 13px courier new; - padding: 5px 5px 14px 5px; - background: white; - border-top: 0px; - overflow:auto; - white-space: pre; - } -* html div.krumo-preview { - padding-top: 2px; - } - -/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ - -li.krumo-footnote { - background:white; - padding: 2px 5px; - list-style:none; - border-top: solid 1px #bebebe; - margin-top:2px; - cursor:default; - } -* html li.krumo-footnote { - line-height: 13px; - } -div.krumo-version { - float:right; - } -li.krumo-footnote h6 { - font: bold 11px verdana; - margin: 0px; - padding: 0px; - color:#E87400; - display:inline; - } -* html li.krumo-footnote h6 { - margin-right: 3px; - } -li.krumo-footnote a { - font: bold 10px arial; - color: #434343; - text-decoration:none; - } -li.krumo-footnote a:hover { - color:black; - } - -li.krumo-footnote span.krumo-call { - font:normal 11px verdana; - position: relative; - top: 1px; - } -li.krumo-footnote span.krumo-call code { - font-weight:bold; - } - -/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ - -div.krumo-title { - font: normal 11px verdana ; - position:relative; - top:9px; - cursor:default; - line-height:2px; - } - -/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ - -strong.krumo-array-length, -strong.krumo-string-length { - font-weight: normal; - } - -/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ +/** +* Krumo "Orange" Skin +* +* @author Kaloyan K. Tsvetkov +*/ + +/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ + +ul.krumo-node { + margin: 0px; + padding: 0px; + } +ul.krumo-node ul { + margin-left: 20px; + } +* html ul.krumo-node ul { + margin-left: 24px; + } +div.krumo-root { + border: solid 1px black; + margin: 1em 0em; + } +ul.krumo-first { + font: normal 12px arial; + border: solid 2px white; + border-top-width:1px; + background: url(%url%bg.gif); + } + +/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ + +li.krumo-child { + display:block; + list-style: none; + padding: 0px; + margin: 0px; + overflow:hidden; + } +div.krumo-element { + cursor:default; + + line-height: 24px; + display:block; + + clear:both; + white-space:nowrap; + + border-top: solid 1px white; + background: #FCEBA9; + padding-left: 10px; + } +* html div.krumo-element { + padding-bottom: 3px; + } +a.krumo-name { + color:#404000; + font: bold 13px Arial; + } +a.krumo-name span.krumo-big { + font: bold 20pt Georgia; + line-height: 14px; + position:relative; + top:2px; + left:-2px; + } +* html a.krumo-name span.krumo-big { + font: bold 19pt Georgia; + top: 5px; + left: 0px; + line-height: 9px; + height: 12px; + padding: 0px; + margin: 0px; + } +div.krumo-expand { + background: #FADB61; + cursor:pointer; + } +div.krumo-hover { + background: #FF8A4B; + } + +/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ + +div.krumo-preview { + font: normal 13px courier new; + padding: 5px 5px 14px 5px; + background: white; + border-top: 0px; + overflow:auto; + white-space: pre; + } +* html div.krumo-preview { + padding-top: 2px; + } + +/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ + +li.krumo-footnote { + background:white; + padding: 2px 5px; + list-style:none; + border-top: solid 1px #bebebe; + margin-top:2px; + cursor:default; + } +* html li.krumo-footnote { + line-height: 13px; + } +div.krumo-version { + float:right; + } +li.krumo-footnote h6 { + font: bold 11px verdana; + margin: 0px; + padding: 0px; + color:#E87400; + display:inline; + } +* html li.krumo-footnote h6 { + margin-right: 3px; + } +li.krumo-footnote a { + font: bold 10px arial; + color: #434343; + text-decoration:none; + } +li.krumo-footnote a:hover { + color:black; + } + +li.krumo-footnote span.krumo-call { + font:normal 11px verdana; + position: relative; + top: 1px; + } +li.krumo-footnote span.krumo-call code { + font-weight:bold; + } + +/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ + +div.krumo-title { + font: normal 11px verdana ; + position:relative; + top:9px; + cursor:default; + line-height:2px; + } + +/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ + +strong.krumo-array-length, +strong.krumo-string-length { + font-weight: normal; + } + +/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ diff --git a/sites/all/modules/contrib/dev/devel/krumo/skins/white/collapsed.gif b/sites/all/modules/contrib/dev/devel/krumo/skins/white/collapsed.gif old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/skins/white/dotted.gif b/sites/all/modules/contrib/dev/devel/krumo/skins/white/dotted.gif old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/skins/white/empty.gif b/sites/all/modules/contrib/dev/devel/krumo/skins/white/empty.gif old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/skins/white/expanded.gif b/sites/all/modules/contrib/dev/devel/krumo/skins/white/expanded.gif old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/dev/devel/krumo/skins/white/skin.css b/sites/all/modules/contrib/dev/devel/krumo/skins/white/skin.css old mode 100755 new mode 100644 index 84f72fd..bbc917c --- a/sites/all/modules/contrib/dev/devel/krumo/skins/white/skin.css +++ b/sites/all/modules/contrib/dev/devel/krumo/skins/white/skin.css @@ -1,165 +1,165 @@ -/** -* Krumo `Schablon.com` Skin -* -* @author Kaloyan K. Tsvetkov -*/ - -/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ - -ul.krumo-node { - margin: 0px; - padding: 0px; - background-color: white; - } -ul.krumo-node ul { - margin-left: 20px; - } -* html ul.krumo-node ul { - margin-left: 24px; - } -div.krumo-root { - border: solid 1px black; - margin: 1em 0em; - } -ul.krumo-first { - font: normal 11px tahoma, verdana; - border: solid 1px white; - } - -/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ - -li.krumo-child { - display:block; - list-style: none; - padding: 0px; - margin: 0px; - overflow:hidden; - } -div.krumo-element { - cursor:default; - display:block; - clear:both; - white-space:nowrap; - - background-color: white; - background-image: url(%url%empty.gif); - background-repeat: no-repeat; - background-position: 6px 5px; - padding: 2px 0px 3px 20px; - } -* html div.krumo-element { - padding-bottom: 3px; - line-height: 13px; - } -div.krumo-expand { - background-image: url(%url%collapsed.gif); - cursor:pointer; - } -div.krumo-hover { - background-color: #BFDFFF; - } -div.krumo-opened { - background-image: url(%url%expanded.gif); - } -a.krumo-name { - color:navy; - font: bold 13px courier new; - line-height:12px; - } -a.krumo-name big { - font: bold 16pt Georgia; - line-height: 10px; - position:relative; - top:2px; - left:-2px; - } -* html a.krumo-name big { - font: bold 15pt Georgia; - float:left; - top: -5px; - left: 0px; - padding: 0px; - margin: 0px; - } -em.krumo-type { - font-style:normal; - margin: 0px 2px; - } - -/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ - -div.krumo-preview { - font: normal 13px courier new; - padding: 5px ; - background: lightyellow; - border: solid 1px #808000; - overflow:auto; - margin: 5px 1em 1em 0px; - white-space: pre; - } -* html div.krumo-preview { - padding-top: 2px; - } - -/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ - -li.krumo-footnote { - background: white url(%url%dotted.gif) repeat-x; - padding: 4px 5px 3px 5px; - list-style:none; - cursor:default; - } -* html li.krumo-footnote { - line-height: 13px; - } -div.krumo-version { - float:right; - } -li.krumo-footnote h6 { - font: bold 11px verdana; - margin: 0px; - padding: 0px; - color:navy; - display:inline; - } -* html li.krumo-footnote h6 { - margin-right: 3px; - } -li.krumo-footnote a { - font: bold 10px arial; - color: #434343; - text-decoration:none; - } -li.krumo-footnote a:hover { - color:black; - } - - -li.krumo-footnote span.krumo-call { - font:normal 11px tahoma, verdana; - position: relative; - top: 1px; - } -li.krumo-footnote span.krumo-call code { - font-weight:bold; - } - -/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ - -div.krumo-title { - font: normal 11px tahoma, verdana; - position:relative; - top:9px; - cursor:default; - line-height:2px; - } - -/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ - -strong.krumo-array-length, -strong.krumo-string-length { - font-weight: normal; - color: #000099; - } - -/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ +/** +* Krumo `Schablon.com` Skin +* +* @author Kaloyan K. Tsvetkov +*/ + +/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ + +ul.krumo-node { + margin: 0px; + padding: 0px; + background-color: white; + } +ul.krumo-node ul { + margin-left: 20px; + } +* html ul.krumo-node ul { + margin-left: 24px; + } +div.krumo-root { + border: solid 1px black; + margin: 1em 0em; + } +ul.krumo-first { + font: normal 11px tahoma, verdana; + border: solid 1px white; + } + +/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ + +li.krumo-child { + display:block; + list-style: none; + padding: 0px; + margin: 0px; + overflow:hidden; + } +div.krumo-element { + cursor:default; + display:block; + clear:both; + white-space:nowrap; + + background-color: white; + background-image: url(%url%empty.gif); + background-repeat: no-repeat; + background-position: 6px 5px; + padding: 2px 0px 3px 20px; + } +* html div.krumo-element { + padding-bottom: 3px; + line-height: 13px; + } +div.krumo-expand { + background-image: url(%url%collapsed.gif); + cursor:pointer; + } +div.krumo-hover { + background-color: #BFDFFF; + } +div.krumo-opened { + background-image: url(%url%expanded.gif); + } +a.krumo-name { + color:navy; + font: bold 13px courier new; + line-height:12px; + } +a.krumo-name span.krumo-big { + font: bold 16pt Georgia; + line-height: 10px; + position:relative; + top:2px; + left:-2px; + } +* html a.krumo-name span.krumo-big { + font: bold 15pt Georgia; + float:left; + top: -5px; + left: 0px; + padding: 0px; + margin: 0px; + } +em.krumo-type { + font-style:normal; + margin: 0px 2px; + } + +/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ + +div.krumo-preview { + font: normal 13px courier new; + padding: 5px ; + background: lightyellow; + border: solid 1px #808000; + overflow:auto; + margin: 5px 1em 1em 0px; + white-space: pre; + } +* html div.krumo-preview { + padding-top: 2px; + } + +/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ + +li.krumo-footnote { + background: white url(%url%dotted.gif) repeat-x; + padding: 4px 5px 3px 5px; + list-style:none; + cursor:default; + } +* html li.krumo-footnote { + line-height: 13px; + } +div.krumo-version { + float:right; + } +li.krumo-footnote h6 { + font: bold 11px verdana; + margin: 0px; + padding: 0px; + color:navy; + display:inline; + } +* html li.krumo-footnote h6 { + margin-right: 3px; + } +li.krumo-footnote a { + font: bold 10px arial; + color: #434343; + text-decoration:none; + } +li.krumo-footnote a:hover { + color:black; + } + + +li.krumo-footnote span.krumo-call { + font:normal 11px tahoma, verdana; + position: relative; + top: 1px; + } +li.krumo-footnote span.krumo-call code { + font-weight:bold; + } + +/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ + +div.krumo-title { + font: normal 11px tahoma, verdana; + position:relative; + top:9px; + cursor:default; + line-height:2px; + } + +/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ + +strong.krumo-array-length, +strong.krumo-string-length { + font-weight: normal; + color: #000099; + } + +/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */ diff --git a/sites/all/modules/contrib/dev/devel/runtests.sh b/sites/all/modules/contrib/dev/devel/runtests.sh new file mode 100755 index 0000000..4eb200a --- /dev/null +++ b/sites/all/modules/contrib/dev/devel/runtests.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env sh + +# This script will run phpunit-based test classes using Drush's +# test framework. First, the Drush executable is located, and +# then phpunit is invoked, passing in drush_testcase.inc as +# the bootstrap file. +# +# Any parameters that may be passed to phpunit may also be used +# with runtests.sh. + +DRUSH_PATH="`which drush`" +DRUSH_DIRNAME="`dirname -- "$DRUSH_PATH"`" + +# if [ $# = 0 ] ; then +# phpunit --bootstrap="$DRUSH_DIRNAME/tests/drush_testcase.inc" . +# else +# phpunit --bootstrap="$DRUSH_DIRNAME/tests/drush_testcase.inc" "$@" +# fi + +#Instead, hard code target file so we don't find a simpletest file at +# /lib/Drupal/devel_generate/Tests/DevelGenerateTest.php. +phpunit --bootstrap="$DRUSH_DIRNAME/tests/drush_testcase.inc" develDrushTest.php diff --git a/sites/all/modules/contrib/dev/entity/README.txt b/sites/all/modules/contrib/dev/entity/README.txt index ed048e4..745dd7f 100644 --- a/sites/all/modules/contrib/dev/entity/README.txt +++ b/sites/all/modules/contrib/dev/entity/README.txt @@ -11,7 +11,15 @@ entity CRUD controller, which helps simplifying the creation of new entity types This is an API module. You only need to enable it if a module depends on it or you are interested in using it for development. -This README is for interested developers. If you are not interested in +Advanced usage: +--------------- +You can optimize cache clearing performance by setting the variable +'entity_rebuild_on_flush' to FALSE. This skips rebuilding of feature +components and exported entities during cache flushing. Instead, it is triggered +by the features module only; e.g., when features are reverted. + + +The README below is for interested developers. If you are not interested in developing, you may stop reading now. -------------------------------------------------------------------------------- diff --git a/sites/all/modules/contrib/dev/entity/ctools/relationships/entity_property.inc b/sites/all/modules/contrib/dev/entity/ctools/relationships/entity_property.inc new file mode 100644 index 0000000..6869146 --- /dev/null +++ b/sites/all/modules/contrib/dev/entity/ctools/relationships/entity_property.inc @@ -0,0 +1,153 @@ + t('Entity property or field (via Entity Metadata Wrapper)'), + 'description' => t('Creates any kind of context for entity properties and fields.'), + 'context' => 'entity_entity_property_context', + 'required context' => new ctools_context_required(t('Entity'), 'entity'), + 'edit form' => 'entity_entity_property_edit_form', + 'edit form validate' => 'entity_entity_property_edit_form_validate', + 'defaults' => array( + 'selector' => '', + 'target_context' => 'entity', + 'concatenator' => ',' + ), +); + +/** + * Return a new context based on an existing context. + */ +function entity_entity_property_context($context, $conf) { + $plugin = $conf['name']; + + // If unset it wants a generic, unfilled context, which is just NULL. + if (empty($context->data)) { + return ctools_context_create_empty(isset($conf['target_context']) ? $conf['target_context'] : 'entity', NULL); + } + + list($part1, $entity_type) = explode(':', $context->plugin); + + if (isset($context->data) && $entity = $context->data) { + // Apply the selector. + $wrapper = entity_metadata_wrapper($entity_type, $entity); + $parts = explode(':', $conf['selector']); + + try { + foreach ($parts as $part) { + if (!($wrapper instanceof EntityStructureWrapper || $wrapper instanceof EntityListWrapper)) { + $wrapper = NULL; + $value = NULL; + continue; + } + $wrapper = $wrapper->get($part); + } + } + catch (EntityMetadataWrapperException $e) { + $wrapper = NULL; + $value = NULL; + } + + if (isset($wrapper)) { + $value = $wrapper->value(); + } + + // Massage list values. + if ($wrapper instanceof EntityListWrapper) { + $value = $wrapper[0]->value(); + $argument = implode($conf['concatenator'], $wrapper->value(array('identifier' => TRUE))); + } + elseif ($wrapper instanceof EntityDrupalWrapper) { + $argument = $wrapper->getIdentifier(); + } + + // Bail out if we were unable to retrieve the value. + if (!isset($value)) { + return ctools_context_create_empty(isset($conf['target_context']) ? $conf['target_context'] : 'entity', NULL); + } + + $context = ctools_context_create($conf['target_context'], $value); + // Provide a suiting argument if context is used as argument. + if (isset($argument)) { + $context->argument = $argument; + } + return $context; + } +} + +function entity_entity_property_edit_form($form, &$form_state) { + $conf = $form_state['conf']; + + $form['selector'] = array( + '#type' => 'textfield', + '#title' => t('Data selector'), + '#description' => t('Any valid data selector, e.g. "title" to select a node\'s title, or "field_tags:1" to select the second tag.'), + '#default_value' => $conf['selector'], + '#required' => TRUE, + ); + $form['concatenator'] = array( + '#title' => t('Concatenator (if multiple)'), + '#type' => 'select', + '#options' => array(',' => ', (AND)', '+' => '+ (OR)'), + '#default_value' => $conf['concatenator'], + '#prefix' => '
    ', + '#suffix' => '
    ', + '#description' => t("When the resulting value is multiple valued and the context is passed on to a view as argument, the value can be concatenated in the form of 1+2+3 (for OR) or 1,2,3 (for AND)."), + ); + return $form; +} + +function entity_entity_property_edit_form_validate($form, &$form_state) { + $context_key = $form_state['values']['context']; + $context = $form_state['contexts'][$context_key]; + $entity_type = $context->type[2]; + + try { + $all_properties = entity_get_all_property_info($entity_type); + $wrapper = entity_metadata_wrapper($entity_type, NULL, array('property info' => $all_properties)); + $parts = explode(':', $form_state['values']['selector']); + foreach ($parts as $part) { + if (!($wrapper instanceof EntityStructureWrapper || $wrapper instanceof EntityListWrapper)) { + form_set_error('selector', t('Unable to apply the data selector part %key.'. array('%key' => $part))); + continue; + } + $wrapper = $wrapper->get($part); + } + $type = entity_entity_property_map_data_type($wrapper->type()); + $form_state['conf']['target_context'] = $type; + } + catch (EntityMetadataWrapperException $e) { + form_set_error('selector', t('Unable to apply the data selector on entity type %type. @reason', array('@reason' => $e->getMessage(), '%type' => $entity_type))); + } + + // Auto-generate a sane identifier. + if ($form_state['values']['identifier'] == $form['identifier']['#default_value']) { + $form_state['values']['identifier'] = 'Entity property ' . $entity_type . ':' . check_plain($form_state['values']['selector']); + } +} + +/** + * Maps an entity-property data type to ctools types. + */ +function entity_entity_property_map_data_type($type) { + // Remove list<> wrappers from types. + while ($item_type = entity_property_list_extract_type($type)) { + $type = $item_type; + } + // Massage data type of entites to c + if (entity_get_info($type)) { + $type = "entity:$type"; + } + if ($type == 'text') { + $type = 'string'; + } + return $type; +} diff --git a/sites/all/modules/contrib/dev/entity/entity.api.php b/sites/all/modules/contrib/dev/entity/entity.api.php index 45f130d..3f77752 100644 --- a/sites/all/modules/contrib/dev/entity/entity.api.php +++ b/sites/all/modules/contrib/dev/entity/entity.api.php @@ -95,9 +95,12 @@ * - path: A path where the UI should show up as expected by hook_menu(). * - controller class: (optional) A controller class name for providing the * UI. Defaults to EntityDefaultUIController, which implements an admin UI - * suiting for managing configuration entities. - * For customizing the UI inherit from the default class and overide methods - * as suiting and specify your class as controller class. + * suiting for managing configuration entities. Other provided controllers + * suiting for content entities are EntityContentUIController or + * EntityBundleableUIController (which work fine despite the poorly named + * 'admin ui' key). + * For customizing the UI inherit from the default class and override + * methods as suiting and specify your class as controller class. * - file: (optional) The name of the file in which the entity form resides * as it is required by hook_menu(). * - file path: (optional) The path to the file as required by hook_menu. If @@ -119,6 +122,13 @@ * Override the controller class to adapt the defaults and to improve and * complete the generated metadata. Set it to FALSE to disable this feature. * Defaults to the EntityDefaultMetadataController class. + * - extra fields controller class: (optional) A controller class for providing + * field API extra fields. Defaults to none. + * The class must implement the EntityExtraFieldsControllerInterface. Display + * extra fields that are exposed that way are rendered by default by the + * EntityAPIController. The EntityDefaultExtraFieldsController class may be + * used to generate extra fields based upon property metadata, which in turn + * get rendered by default by the EntityAPIController. * - features controller class: (optional) A controller class for providing * Features module integration for exportable entities. The given class has to * inherit from the default class being EntityDefaultFeaturesController. Set @@ -145,10 +155,11 @@ * edit form for your entity type. See entity_form(). * In case the 'admin ui' is used, no callback needs to be specified. * - entity cache: (optional) Whether entities should be cached using the cache - * system. Requires the entitycache module to be installed and enabled. As - * cached entities are only retrieved by id key, the cache would not apply to - * exportable entities retrieved by name key. If enabled, 'field cache' is - * obsolete and should be disabled. Defaults to FALSE. + * system. Requires the entitycache module to be installed and enabled and the + * module key to be specified. As cached entities are only retrieved by id key, + * the cache would not apply to exportable entities retrieved by name key. + * If enabled and the entitycache module is active, 'field cache' is obsolete + * and is automatically disabled. Defaults to FALSE. * * @see hook_entity_info() * @see entity_metadata_hook_entity_info() diff --git a/sites/all/modules/contrib/dev/entity/entity.features.inc b/sites/all/modules/contrib/dev/entity/entity.features.inc index d856863..0ec3e61 100644 --- a/sites/all/modules/contrib/dev/entity/entity.features.inc +++ b/sites/all/modules/contrib/dev/entity/entity.features.inc @@ -7,6 +7,12 @@ /** * Returns the configured entity features controller. + * + * @param string $type + * The entity type to get the controller for. + * + * @return EntityDefaultFeaturesController + * The configured entity features controller. */ function entity_features_get_controller($type) { $static = &drupal_static(__FUNCTION__); @@ -85,6 +91,7 @@ class EntityDefaultFeaturesController { $fields = field_info_instances($this->info['bundle of'], $entity->{$this->bundleKey}); foreach ($fields as $name => $field) { $pipe['field'][] = "{$field['entity_type']}-{$field['bundle']}-{$field['field_name']}"; + $pipe['field_instance'][] = "{$field['entity_type']}-{$field['bundle']}-{$field['field_name']}"; } } } @@ -125,9 +132,7 @@ class EntityDefaultFeaturesController { */ function revert($module = NULL) { if ($defaults = features_get_default($this->type, $module)) { - foreach ($defaults as $name => $entity) { - entity_delete($this->type, $name); - } + entity_delete_multiple($this->type, array_keys($defaults)); } } } @@ -146,13 +151,21 @@ function entity_features_api() { } /** + * Implements hook_features_export_options(). + * * Features component callback. */ -function entity_features_export_options($entity_type) { +function entity_features_export_options($a1, $a2 = NULL) { + // Due to a change in the Features API the first parameter might be a feature + // object or an entity type, depending on the Features version. This check is + // for backwards compatibility. + $entity_type = is_string($a1) ? $a1 : $a2; return entity_features_get_controller($entity_type)->export_options(); } /** + * Implements hook_features_export(). + * * Features component callback. */ function entity_features_export($data, &$export, $module_name = '', $entity_type) { @@ -160,6 +173,8 @@ function entity_features_export($data, &$export, $module_name = '', $entity_type } /** + * Implements hook_features_export_render(). + * * Features component callback. */ function entity_features_export_render($module, $data, $export = NULL, $entity_type) { @@ -167,8 +182,23 @@ function entity_features_export_render($module, $data, $export = NULL, $entity_t } /** + * Implements hook_features_revert(). + * * Features component callback. */ function entity_features_revert($module = NULL, $entity_type) { return entity_features_get_controller($entity_type)->revert($module); } + +/** + * Implements hook_features_post_restore(). + * + * Rebuild all defaults when a features rebuild is triggered - even the ones not + * handled by features itself. + */ +function entity_features_post_restore($op, $items = array()) { + if ($op == 'rebuild') { + // Use features rebuild to rebuild the features independent exports too. + entity_defaults_rebuild(); + } +} diff --git a/sites/all/modules/contrib/dev/entity/entity.info b/sites/all/modules/contrib/dev/entity/entity.info index 9d0e4a7..0b865a0 100644 --- a/sites/all/modules/contrib/dev/entity/entity.info +++ b/sites/all/modules/contrib/dev/entity/entity.info @@ -25,9 +25,9 @@ files[] = views/handlers/entity_views_handler_field_uri.inc files[] = views/handlers/entity_views_handler_relationship_by_bundle.inc files[] = views/handlers/entity_views_handler_relationship.inc files[] = views/plugins/entity_views_plugin_row_entity_view.inc -; Information added by drupal.org packaging script on 2013-04-30 -version = "7.x-1.1" +; Information added by Drupal.org packaging script on 2018-02-14 +version = "7.x-1.9" core = "7.x" project = "entity" -datestamp = "1367338216" +datestamp = "1518620551" diff --git a/sites/all/modules/contrib/dev/entity/entity.info.inc b/sites/all/modules/contrib/dev/entity/entity.info.inc index 1f626cc..30da655 100644 --- a/sites/all/modules/contrib/dev/entity/entity.info.inc +++ b/sites/all/modules/contrib/dev/entity/entity.info.inc @@ -169,6 +169,7 @@ function _entity_metadata_convert_schema_type($type) { switch ($type) { case 'int': case 'serial': + case 'date': return 'integer'; case 'float': case 'numeric': @@ -179,3 +180,86 @@ function _entity_metadata_convert_schema_type($type) { return 'text'; } } + +/** + * Interface for extra fields controller. + * + * Note: Displays extra fields exposed by this controller are rendered by + * default by the EntityAPIController. + */ +interface EntityExtraFieldsControllerInterface { + + /** + * Returns extra fields for this entity type. + * + * @see hook_field_extra_fields(). + */ + public function fieldExtraFields(); +} + +/** + * Default controller for generating extra fields based on property metadata. + * + * By default a display extra field for each property not being a field, ID or + * bundle is generated. + */ +class EntityDefaultExtraFieldsController implements EntityExtraFieldsControllerInterface { + + /** + * @var string + */ + protected $entityType; + + /** + * @var array + */ + protected $entityInfo; + + /** + * Constructor. + */ + public function __construct($type) { + $this->entityType = $type; + $this->entityInfo = entity_get_info($type); + $this->propertyInfo = entity_get_property_info($type); + } + + /** + * Implements EntityExtraFieldsControllerInterface::fieldExtraFields(). + */ + public function fieldExtraFields() { + $extra = array(); + foreach ($this->propertyInfo['properties'] as $name => $property_info) { + // Skip adding the ID or bundle. + if ($this->entityInfo['entity keys']['id'] == $name || $this->entityInfo['entity keys']['bundle'] == $name) { + continue; + } + $extra[$this->entityType][$this->entityType]['display'][$name] = $this->generateExtraFieldInfo($name, $property_info); + } + + // Handle bundle properties. + $this->propertyInfo += array('bundles' => array()); + foreach ($this->propertyInfo['bundles'] as $bundle_name => $info) { + foreach ($info['properties'] as $name => $property_info) { + if (empty($property_info['field'])) { + $extra[$this->entityType][$bundle_name]['display'][$name] = $this->generateExtraFieldInfo($name, $property_info); + } + } + } + return $extra; + } + + /** + * Generates the display field info for a given property. + */ + protected function generateExtraFieldInfo($name, $property_info) { + $info = array( + 'label' => $property_info['label'], + 'weight' => 0, + ); + if (!empty($property_info['description'])) { + $info['description'] = $property_info['description']; + } + return $info; + } +} diff --git a/sites/all/modules/contrib/dev/entity/entity.install b/sites/all/modules/contrib/dev/entity/entity.install index 2a992e9..118820b 100644 --- a/sites/all/modules/contrib/dev/entity/entity.install +++ b/sites/all/modules/contrib/dev/entity/entity.install @@ -5,6 +5,22 @@ * Install file for the entity API. */ +/** + * Implements hook_enable(). + */ +function entity_enable() { + // Create cache tables for entities that support Entity cache module. + entity_entitycache_installed_modules(); +} + +/** + * Implements hook_uninstall(). + */ +function entity_uninstall() { + // Delete variables. + variable_del('entity_rebuild_on_flush'); +} + /** * The entity API modules have been merged into a single module. */ @@ -26,3 +42,101 @@ function entity_update_7001() { function entity_update_7002() { // Do nothing, update.php clears cache for us in case there is an update. } + +/** + * Create cache tables for entities that support Entity cache module. + */ +function entity_update_7003() { + entity_entitycache_installed_modules(); +} + +/** + * Create cache tables for entities of modules that support Entity cache module. + * + * @param $modules + * (optional) An array of module names that have been installed. + * If not specified, try to add cache tables for all modules. + */ +function entity_entitycache_installed_modules($modules = NULL) { + if (!module_exists('entitycache')) { + return; + } + + // If no modules are specified or if entitycache is being installed, + // try to add entitycache tables for supporting entities of all modules. + if (!isset($modules) || in_array('entitycache', $modules)) { + $modules = module_list(); + } + + // Get all installed modules that support entity cache. + $entitycache_module_info = _entity_entitycache_get_module_info($modules); + + // For uninstallation of modules, we need to keep a list of tables we created + // per module providing the entity type. + $tables_created = variable_get('entity_cache_tables_created'); + + foreach ($entitycache_module_info as $module => $module_entitycache_entities) { + foreach ($module_entitycache_entities as $entity_type => $entity_info) { + // Do not break modules that create the cache tables for themselves. + if (!db_table_exists('cache_entity_' . $entity_type)) { + $schema = drupal_get_schema_unprocessed('system', 'cache'); + $schema['description'] = 'Cache table used to store' . $entity_type . ' entity records.'; + db_create_table('cache_entity_' . $entity_type, $schema); + $tables_created[$module][] = 'cache_entity_' . $entity_type; + } + } + } + variable_set('entity_cache_tables_created', $tables_created); +} + +/** + * Remove entity cache tables for entity types of uninstalled modules. + * + * @param $modules + * (optional) An array of uninstalled modules. If not specified, try to remove + * cache tables for all modules. + */ +function entity_entitycache_uninstalled_modules($modules = NULL) { + // If no modules are specified or if entitycache is being uninstalled, + // try to remove entitycache tables for supporting entities of all modules. + if (!isset($modules) || in_array('entitycache', $modules)) { + $modules = module_list(); + } + $tables_created = variable_get('entity_cache_tables_created'); + foreach ($modules as $module) { + if (!empty($tables_created[$module])) { + foreach ($tables_created[$module] as $table) { + db_drop_table($table); + } + unset($tables_created[$module]); + } + } + variable_set('entity_cache_tables_created', $tables_created); +} + +/** + * Helper to fetch entity info about entity types that use caching. + */ +function _entity_entitycache_get_module_info($modules) { + // Prepare a keyed array of all modules with their entity types and infos. + // Structure: [module][entity][info] + $entity_crud_info = entity_crud_get_info(); + $info = array(); + foreach ($entity_crud_info as $entity_name => $entity_info) { + // Make sure that the entity info specifies a module and supports entitycache. + if (!isset($entity_info['module']) || empty($entity_info['entity cache'])) { + continue; + } + $module = $entity_info['module']; + // Only treat installed modules. + if (!in_array($module, $modules)) { + continue; + } + // Add the entity info to the module key. + if (!isset($info[$module])) { + $info[$module] = array(); + } + $info[$module][$entity_name] = $entity_info; + } + return $info; +} diff --git a/sites/all/modules/contrib/dev/entity/entity.module b/sites/all/modules/contrib/dev/entity/entity.module index 81ad379..da0acf6 100644 --- a/sites/all/modules/contrib/dev/entity/entity.module +++ b/sites/all/modules/contrib/dev/entity/entity.module @@ -113,6 +113,106 @@ function entity_object_load($entity_id, $entity_type) { return reset($entities); } +/** + * Page callback to show links to add an entity of a specific bundle. + * + * Entity modules that provide a further description to their bundles may wish + * to implement their own version of this to show these. + * + * @param $entity_type + * The type of the entity. + */ +function entity_ui_bundle_add_page($entity_type) { + // Set the title, as we're a MENU_LOCAL_ACTION and hence just get tab titles. + module_load_include('inc', 'entity', 'includes/entity.ui'); + drupal_set_title(entity_ui_get_action_title('add', $entity_type)); + + // Get entity info for our bundles. + $info = entity_get_info($entity_type); + $items = array(); + foreach ($info['bundles'] as $bundle_name => $bundle_info) { + // Create an empty entity with just the bundle set to check for access. + $dummy_entity = entity_create($entity_type, array( + $info['entity keys']['bundle'] => $bundle_name, + )); + // If modules use a uid, they can default to the current-user + // in their create() method on the storage controller. + if (entity_access('create', $entity_type, $dummy_entity, $account = NULL)) { + $add_path = $info['admin ui']['path'] . '/add/' . $bundle_name; + $items[] = l(t('Add @label', array('@label' => $bundle_info['label'])), $add_path); + } + } + return theme('item_list', array('items' => $items)); +} + +/** + * Page callback to add an entity of a specific bundle. + * + * @param $entity_type + * The type of the entity. + * @param $bundle_name + * The bundle machine name. + */ +function entity_ui_get_bundle_add_form($entity_type, $bundle_name) { + $info = entity_get_info($entity_type); + $bundle_key = $info['entity keys']['bundle']; + + // Make a stub entity of the right bundle to pass to the entity_ui_get_form(). + $values = array( + $bundle_key => $bundle_name, + ); + $entity = entity_create($entity_type, $values); + + return entity_ui_get_form($entity_type, $entity, 'add'); +} + +/** + * Page callback for viewing an entity. + * + * @param Entity $entity + * The entity to be rendered. + * + * @return array + * A renderable array of the entity in full view mode. + */ +function entity_ui_entity_page_view($entity) { + module_load_include('inc', 'entity', 'includes/entity.ui'); + return $entity->view('full', NULL, TRUE); +} + +/** + * Gets the page title for the passed operation. + */ +function entity_ui_get_page_title($op, $entity_type, $entity = NULL) { + if (isset($entity)) { + module_load_include('inc', 'entity', 'includes/entity.ui'); + $label = entity_label($entity_type, $entity); + list(, , $bundle) = entity_extract_ids($entity_type, $entity); + } + else { + $info = entity_get_info($entity_type); + $label = $info['label']; + $bundle = NULL; + } + + switch ($op) { + case 'view': + return $label; + case 'edit': + return t('Edit @label', array('@label' => $label)); + case 'clone': + return t('Clone @label', array('@label' => $label)); + case 'revert': + return t('Revert @label', array('@label' => $label)); + case 'delete': + return t('Delete @label', array('@label' => $label)); + case 'export': + return t('Export @label', array('@label' => $label)); + } + + return entity_ui_get_action_title($op, $entity_type, $bundle); +} + /** * A wrapper around entity_load() to load a single entity by name or numeric id. * @@ -504,7 +604,7 @@ function entity_id($entity_type, $entity) { * content language of the current request. * @param $page * (optional) If set will control if the entity is rendered: if TRUE - * the entity will be rendered without its title, so that it can be embeded + * the entity will be rendered without its title, so that it can be embedded * in another context. If FALSE the entity will be displayed with its title * in a mode suitable for lists. * If unset, the page mode will be enabled if the current path is the URI @@ -529,7 +629,15 @@ function entity_view($entity_type, $entities, $view_mode = 'full', $langcode = N } /** - * Determines whether the given user has access to an entity. + * Determines whether the given user can perform actions on an entity. + * + * For create operations, the pattern is to create an entity and then + * check if the user has create access. + * + * @code + * $node = entity_create('node', array('type' => 'page')); + * $access = entity_access('create', 'node', $node, $account); + * @endcode * * @param $op * The operation being performed. One of 'view', 'update', 'create' or @@ -828,7 +936,12 @@ function _entity_defaults_rebuild($entity_type) { // implementations. $originals[$name] = $entity->original; - $entity->{$keys['status']} |= ENTITY_IN_CODE; + if (!isset($entity->{$keys['status']})) { + $entity->{$keys['status']} = ENTITY_IN_CODE; + } + else { + $entity->{$keys['status']} |= ENTITY_IN_CODE; + } $entity->is_rebuild = TRUE; entity_save($entity_type, $entity); unset($entity->is_rebuild); @@ -842,6 +955,22 @@ function _entity_defaults_rebuild($entity_type) { } } +/** + * Implements hook_modules_installed(). + */ +function entity_modules_installed($modules) { + module_load_install('entity'); + entity_entitycache_installed_modules($modules); +} + +/** + * Implements hook_modules_uninstalled(). + */ +function entity_modules_uninstalled($modules) { + module_load_install('entity'); + entity_entitycache_uninstalled_modules($modules); +} + /** * Implements hook_modules_enabled(). */ @@ -951,9 +1080,22 @@ function entity_flush_caches() { // case of enabling or disabling modules we already rebuild defaults in // entity_modules_enabled() and entity_modules_disabled(), so we do not need // to do it again. - if (current_path() != 'admin/modules/list/confirm') { + // Also check if rebuilding on cache flush is explicitly disabled. + if (current_path() != 'admin/modules/list/confirm' && variable_get('entity_rebuild_on_flush', TRUE)) { entity_defaults_rebuild(); } + + // Care about entitycache tables. + if (module_exists('entitycache')) { + $tables = array(); + $tables_created = variable_get('entity_cache_tables_created'); + if (is_array($tables_created)) { + foreach ($tables_created as $module => $entity_cache_tables) { + $tables = array_merge($tables, $entity_cache_tables); + } + } + return $tables; + } } /** @@ -971,11 +1113,18 @@ function entity_theme() { return array( 'entity_status' => array( 'variables' => array('status' => NULL, 'html' => TRUE), + 'file' => 'theme/entity.theme.inc', ), 'entity' => array( 'render element' => 'elements', 'template' => 'entity', 'pattern' => $pattern, + 'path' => drupal_get_path('module', 'entity') . '/theme', + 'file' => 'entity.theme.inc', + ), + 'entity_property' => array( + 'render element' => 'elements', + 'file' => 'theme/entity.theme.inc', ), 'entity_ui_overview_item' => array( 'variables' => array('label' => NULL, 'entity_type' => NULL, 'url' => FALSE, 'name' => FALSE), @@ -984,90 +1133,6 @@ function entity_theme() { ); } -/** - * Themes the exportable status of an entity. - */ -function theme_entity_status($variables) { - $status = $variables['status']; - $html = $variables['html']; - if (($status & ENTITY_FIXED) == ENTITY_FIXED) { - $label = t('Fixed'); - $help = t('The configuration is fixed and cannot be changed.'); - return $html ? "" . $label . "" : $label; - } - elseif (($status & ENTITY_OVERRIDDEN) == ENTITY_OVERRIDDEN) { - $label = t('Overridden'); - $help = t('This configuration is provided by a module, but has been changed.'); - return $html ? "" . $label . "" : $label; - } - elseif ($status & ENTITY_IN_CODE) { - $label = t('Default'); - $help = t('A module provides this configuration.'); - return $html ? "" . $label . "" : $label; - } - elseif ($status & ENTITY_CUSTOM) { - $label = t('Custom'); - $help = t('A custom configuration by a user.'); - return $html ? "" . $label . "" : $label; - } -} - -/** - * Process variables for entity.tpl.php. - */ -function template_preprocess_entity(&$variables) { - $variables['view_mode'] = $variables['elements']['#view_mode']; - $entity_type = $variables['elements']['#entity_type']; - $variables['entity_type'] = $entity_type; - $entity = $variables['elements']['#entity']; - $variables[$variables['elements']['#entity_type']] = $entity; - $info = entity_get_info($entity_type); - - $variables['title'] = check_plain(entity_label($entity_type, $entity)); - - $uri = entity_uri($entity_type, $entity); - $variables['url'] = $uri ? url($uri['path'], $uri['options']) : FALSE; - - if (isset($variables['elements']['#page'])) { - // If set by the caller, respect the page property. - $variables['page'] = $variables['elements']['#page']; - } - else { - // Else, try to automatically detect it. - $variables['page'] = $uri && $uri['path'] == $_GET['q']; - } - - // Helpful $content variable for templates. - $variables['content'] = array(); - foreach (element_children($variables['elements']) as $key) { - $variables['content'][$key] = $variables['elements'][$key]; - } - - if (!empty($info['fieldable'])) { - // Make the field variables available with the appropriate language. - field_attach_preprocess($entity_type, $entity, $variables['content'], $variables); - } - list(, , $bundle) = entity_extract_ids($entity_type, $entity); - - // Gather css classes. - $variables['classes_array'][] = drupal_html_class('entity-' . $entity_type); - $variables['classes_array'][] = drupal_html_class($entity_type . '-' . $bundle); - - // Add RDF type and about URI. - if (module_exists('rdf')) { - $variables['attributes_array']['about'] = empty($uri['path']) ? NULL: url($uri['path']); - $variables['attributes_array']['typeof'] = empty($entity->rdf_mapping['rdftype']) ? NULL : $entity->rdf_mapping['rdftype']; - } - - // Add suggestions. - $variables['theme_hook_suggestions'][] = $entity_type; - $variables['theme_hook_suggestions'][] = $entity_type . '__' . $bundle; - $variables['theme_hook_suggestions'][] = $entity_type . '__' . $bundle . '__' . $variables['view_mode']; - if ($id = entity_id($entity_type, $entity)) { - $variables['theme_hook_suggestions'][] = $entity_type . '__' . $id; - } -} - /** * Label callback that refers to the entity classes label method. */ @@ -1218,6 +1283,37 @@ function entity_ui_get_form($entity_type, $entity, $op = 'edit', $form_state = a return drupal_build_form($form_id, $form_state); } + +/** + * Gets the page/menu title for local action operations. + * + * @param $op + * The current operation. One of 'add' or 'import'. + * @param $entity_type + * The entity type. + * @param $bundle_name + * (Optional) The name of the bundle. May be NULL if the bundle name is not + * relevant to the current page. If the entity type has only one bundle, or no + * bundles, this will be the same as the entity type. + */ +function entity_ui_get_action_title($op, $entity_type, $bundle_name = NULL) { + $info = entity_get_info($entity_type); + switch ($op) { + case 'add': + if (isset($bundle_name) && $bundle_name != $entity_type) { + return t('Add @bundle_name @entity_type', array( + '@bundle_name' => drupal_strtolower($info['bundles'][$bundle_name]['label']), + '@entity_type' => drupal_strtolower($info['label']), + )); + } + else { + return t('Add @entity_type', array('@entity_type' => drupal_strtolower($info['label']))); + } + case 'import': + return t('Import @entity_type', array('@entity_type' => drupal_strtolower($info['label']))); + } +} + /** * Helper for using i18n_string(). * @@ -1245,11 +1341,44 @@ function entity_views_api() { ); } +/** + * Implements hook_field_extra_fields(). + */ +function entity_field_extra_fields() { + // Invoke specified controllers for entity types provided by the CRUD API. + $items = array(); + foreach (entity_crud_get_info() as $type => $info) { + if (!empty($info['extra fields controller class'])) { + $items = array_merge_recursive($items, entity_get_extra_fields_controller($type)->fieldExtraFields()); + } + } + return $items; +} + +/** + * Gets the extra field controller class for a given entity type. + * + * @return EntityExtraFieldsControllerInterface|false + * The controller for the given entity type or FALSE if none is specified. + */ +function entity_get_extra_fields_controller($type = NULL) { + $static = &drupal_static(__FUNCTION__); + + if (!isset($static[$type])) { + $static[$type] = FALSE; + $info = entity_get_info($type); + if (!empty($info['extra fields controller class'])) { + $static[$type] = new $info['extra fields controller class']($type); + } + } + return $static[$type]; +} + /** * Returns a property wrapper for the given data. * * If an entity is wrapped, the wrapper can be used to retrieve further wrappers - * for the entitity properties. For that the wrapper support chaining, e.g. you + * for the entity properties. For that the wrapper support chaining, e.g. you * can use a node wrapper to get the node authors mail address: * * @code @@ -1347,6 +1476,13 @@ function entity_entity_info_alter(&$entity_info) { if (!isset($info['configuration'])) { $entity_info[$type]['configuration'] = !empty($info['exportable']); } + + if (isset($info['controller class']) && in_array('EntityAPIControllerInterface', class_implements($info['controller class']))) { + // Automatically disable field cache when entity cache is used. + if (!empty($info['entity cache']) && module_exists('entitycache')) { + $entity_info[$type]['field cache'] = FALSE; + } + } } } @@ -1387,6 +1523,11 @@ function _entity_info_add_metadata(&$entity_info) { $entity_info['node']['form callback'] = 'entity_metadata_form_node'; $entity_info['user']['form callback'] = 'entity_metadata_form_user'; + // URI callbacks. + if (!isset($entity_info['file']['uri callback'])) { + $entity_info['file']['uri callback'] = 'entity_metadata_uri_file'; + } + // View callbacks. $entity_info['node']['view callback'] = 'entity_metadata_view_node'; $entity_info['user']['view callback'] = 'entity_metadata_view_single'; @@ -1428,7 +1569,7 @@ function _entity_info_add_metadata(&$entity_info) { * Implements hook_ctools_plugin_directory(). */ function entity_ctools_plugin_directory($module, $plugin) { - if ($module == 'ctools' && $plugin == 'content_types') { - return 'ctools/content_types'; + if ($module == 'ctools') { + return "ctools/$plugin"; } } diff --git a/sites/all/modules/contrib/dev/entity/entity.test b/sites/all/modules/contrib/dev/entity/entity.test index 746713e..fd8cea1 100644 --- a/sites/all/modules/contrib/dev/entity/entity.test +++ b/sites/all/modules/contrib/dev/entity/entity.test @@ -535,6 +535,57 @@ class EntityAPIRulesIntegrationTestCase extends EntityWebTestCase { } } +/** + * Tests comments with node access. + */ +class EntityAPICommentNodeAccessTestCase extends CommentHelperCase { + + public static function getInfo() { + return array( + 'name' => 'Entity API comment node access', + 'description' => 'Test viewing comments on nodes with node access.', + 'group' => 'Entity API', + ); + } + + function setUp() { + DrupalWebTestCase::setUp('comment', 'entity', 'node_access_test'); + node_access_rebuild(); + + // Create test node and user with simple node access permission. The + // 'node test view' permission is implemented and granted by the + // node_access_test module. + $this->accessUser = $this->drupalCreateUser(array('access comments', 'post comments', 'edit own comments', 'node test view')); + $this->noAccessUser = $this->drupalCreateUser(array('administer comments')); + $this->node = $this->drupalCreateNode(array('type' => 'article', 'uid' => $this->accessUser->uid)); + } + + /** + * Tests comment access when node access is enabled. + */ + function testCommentNodeAccess() { + // Post comment. + $this->drupalLogin($this->accessUser); + $comment_text = $this->randomName(); + $comment = $this->postComment($this->node, $comment_text); + $comment_loaded = comment_load($comment->id); + $this->assertTrue($this->commentExists($comment), 'Comment found.'); + $this->drupalLogout(); + + // Check access to node and associated comment for access user. + $this->assertTrue(entity_access('view', 'node', $this->node, $this->accessUser), 'Access to view node was granted for access user'); + $this->assertTrue(entity_access('view', 'comment', $comment_loaded, $this->accessUser), 'Access to view comment was granted for access user'); + $this->assertTrue(entity_access('update', 'comment', $comment_loaded, $this->accessUser), 'Access to update comment was granted for access user'); + $this->assertFalse(entity_access('delete', 'comment', $comment_loaded, $this->accessUser), 'Access to delete comment was denied for access user'); + + // Check access to node and associated comment for no access user. + $this->assertFalse(entity_access('view', 'node', $this->node, $this->noAccessUser), 'Access to view node was denied for no access user'); + $this->assertFalse(entity_access('view', 'comment', $comment_loaded, $this->noAccessUser), 'Access to view comment was denied for no access user'); + $this->assertFalse(entity_access('update', 'comment', $comment_loaded, $this->noAccessUser), 'Access to update comment was denied for no access user'); + $this->assertFalse(entity_access('delete', 'comment', $comment_loaded, $this->noAccessUser), 'Access to delete comment was denied for no access user'); + } +} + /** * Test the i18n integration. */ @@ -946,6 +997,36 @@ class EntityMetadataTestCase extends EntityWebTestCase { // Test field level access. $this->assertTrue($wrapper->{$this->field_name}->access('view'), 'Field access granted.'); + + // Create node owned by anonymous and test access() method on each of its + // properties. + $node = $this->drupalCreateNode(array('type' => 'page', 'uid' => 0)); + $wrapper = entity_metadata_wrapper('node', $node->nid); + foreach ($wrapper as $name => $property) { + $property->access('view'); + } + + // Property access of entity references takes entity access into account. + $node = $this->drupalCreateNode(array('type' => 'article')); + $wrapper = entity_metadata_wrapper('node', $node); + $unprivileged_user = $this->drupalCreateUser(); + $privileged_user = $this->drupalCreateUser(array('access user profiles')); + + $this->assertTrue($wrapper->author->access('view', $privileged_user)); + $this->assertFalse($wrapper->author->access('view', $unprivileged_user)); + + // Ensure the same works with multiple entity references by testing the + // $node->field_tags example. + $privileged_user = $this->drupalCreateUser(array('administer taxonomy')); + // Terms are view-able with access content, so make sure to remove this + // permission first. + user_role_revoke_permissions(DRUPAL_ANONYMOUS_RID, array('access content')); + $unprivileged_user = drupal_anonymous_user(); + + $this->assertTrue($wrapper->field_tags->access('view', $privileged_user), 'Privileged user has access.'); + $this->assertTrue($wrapper->field_tags->access('view', $unprivileged_user), 'Unprivileged user has access.'); + $this->assertTrue($wrapper->field_tags[0]->access('view', $privileged_user), 'Privileged user has access.'); + $this->assertFalse($wrapper->field_tags[0]->access('view', $unprivileged_user), 'Unprivileged user has no access.'); } /** @@ -1036,6 +1117,299 @@ class EntityMetadataTestCase extends EntityWebTestCase { } } +/** + * Tests basic entity_access() functionality for nodes. + * + * This code is a modified version of NodeAccessTestCase. + * + * @see NodeAccessTestCase + */ +class EntityMetadataNodeAccessTestCase extends EntityWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Entity Metadata Node Access', + 'description' => 'Test entity_access() for nodes', + 'group' => 'Entity API', + ); + } + + /** + * Asserts node_access() correctly grants or denies access. + */ + function assertNodeMetadataAccess($ops, $node, $account) { + foreach ($ops as $op => $result) { + $msg = t("entity_access() returns @result with operation '@op'.", array('@result' => $result ? 'TRUE' : 'FALSE', '@op' => $op)); + $access = entity_access($op, 'node', $node, $account); + $this->assertEqual($result, $access, $msg); + } + } + + function setUp() { + parent::setUp('entity', 'node'); + // Clear permissions for authenticated users. + db_delete('role_permission') + ->condition('rid', DRUPAL_AUTHENTICATED_RID) + ->execute(); + } + + /** + * Runs basic tests for entity_access() function. + */ + function testNodeMetadataAccess() { + // Author user. + $node_author_account = $this->drupalCreateUser(array()); + // Make a node object. + $settings = array( + 'uid' => $node_author_account->uid, + 'type' => 'page', + 'title' => 'Node ' . $this->randomName(32), + ); + $node = $this->drupalCreateNode($settings); + + // Ensures user without 'access content' permission can do nothing. + $web_user1 = $this->drupalCreateUser(array('create page content', 'edit any page content', 'delete any page content')); + $this->assertNodeMetadataAccess(array('create' => FALSE, 'view' => FALSE, 'update' => FALSE, 'delete' => FALSE), $node, $web_user1); + + // Ensures user with 'bypass node access' permission can do everything. + $web_user2 = $this->drupalCreateUser(array('bypass node access')); + $this->assertNodeMetadataAccess(array('create' => TRUE, 'view' => TRUE, 'update' => TRUE, 'delete' => TRUE), $node, $web_user2); + + // User cannot 'view own unpublished content'. + $web_user3 = $this->drupalCreateUser(array('access content')); + // Create an unpublished node. + $settings = array('type' => 'page', 'status' => 0, 'uid' => $web_user3->uid); + $node_unpublished = $this->drupalCreateNode($settings); + $this->assertNodeMetadataAccess(array('view' => FALSE), $node_unpublished, $web_user3); + // User cannot create content without permission. + $this->assertNodeMetadataAccess(array('create' => FALSE), $node, $web_user3); + + // User can 'view own unpublished content', but another user cannot. + $web_user4 = $this->drupalCreateUser(array('access content', 'view own unpublished content')); + $web_user5 = $this->drupalCreateUser(array('access content', 'view own unpublished content')); + $node4 = $this->drupalCreateNode(array('status' => 0, 'uid' => $web_user4->uid)); + $this->assertNodeMetadataAccess(array('view' => TRUE, 'update' => FALSE), $node4, $web_user4); + $this->assertNodeMetadataAccess(array('view' => FALSE), $node4, $web_user5); + + // Tests the default access provided for a published node. + $node5 = $this->drupalCreateNode(); + $this->assertNodeMetadataAccess(array('view' => TRUE, 'update' => FALSE, 'delete' => FALSE, 'create' => FALSE), $node5, $web_user3); + } +} + +/** + * Test user permissions for node creation. + */ +class EntityMetadataNodeCreateAccessTestCase extends EntityWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Entity Metadata Node Create Access', + 'description' => 'Test entity_access() for nodes', + 'group' => 'Entity API', + ); + } + + function setUp() { + parent::setUp('entity', 'node'); + } + + /** + * Addresses the special case of 'create' access for nodes. + */ + public function testNodeMetadataCreateAccess() { + // Create some users. One with super-powers, one with create perms, + // and one with no perms, and a different one to author the node. + $admin_account = $this->drupalCreateUser(array( + 'bypass node access', + )); + $creator_account = $this->drupalCreateUser(array( + 'create page content', + )); + $auth_only_account = $this->drupalCreateUser(array()); + $node_author_account = $this->drupalCreateUser(array()); + + // Make a node object with Entity API (contrib) + $settings = array( + 'uid' => $node_author_account->uid, + 'type' => 'page', + 'title' => $this->randomName(32), + 'body' => array(LANGUAGE_NONE => array(array($this->randomName(64)))), + ); + $node = entity_create('node', $settings); + + // Test the populated wrapper. + $wrapper = entity_metadata_wrapper('node', $node); + $this->assertTrue($wrapper->entityAccess('create', $admin_account), 'Create access allowed for ADMIN, for populated wrapper.'); + $this->assertTrue($wrapper->entityAccess('create', $creator_account), 'Create access allowed for CREATOR, for populated wrapper.'); + $this->assertFalse($wrapper->entityAccess('create', $auth_only_account), 'Create access denied for USER, for populated wrapper.'); + + // Test entity_acces(). + $this->assertTrue(entity_access('create', 'node', $node, $admin_account), 'Create access allowed for ADMIN, for entity_access().'); + $this->assertTrue(entity_access('create', 'node', $node, $creator_account), 'Create access allowed for CREATOR, for entity_access().'); + $this->assertFalse(entity_access('create', 'node', $node, $auth_only_account), 'Create access denied for USER, for entity_access().'); + } +} + +/** + * Tests user permissions for node revisions. + * + * Based almost entirely on NodeRevisionPermissionsTestCase. + */ +class EntityMetadataNodeRevisionAccessTestCase extends DrupalWebTestCase { + protected $node_revisions = array(); + protected $accounts = array(); + + // Map revision permission names to node revision access ops. + protected $map = array( + 'view' => 'view revisions', + 'update' => 'revert revisions', + 'delete' => 'delete revisions', + ); + + public static function getInfo() { + return array( + 'name' => 'Entity Metadata Node Revision Access', + 'description' => 'Tests user permissions for node revision operations.', + 'group' => 'Entity API', + ); + } + + function setUp() { + parent::setUp('entity', 'node'); + + // Create a node with several revisions. + $node = $this->drupalCreateNode(); + $this->node_revisions[] = $node; + + for ($i = 0; $i < 3; $i++) { + // Create a revision for the same nid and settings with a random log. + $revision = node_load($node->nid); + $revision->revision = 1; + $revision->log = $this->randomName(32); + node_save($revision); + $this->node_revisions[] = node_load($revision->nid); + } + + // Create three users, one with each revision permission. + foreach ($this->map as $op => $permission) { + // Create the user. + $account = $this->drupalCreateUser( + array( + 'access content', + 'edit any page content', + 'delete any page content', + $permission, + ) + ); + $account->op = $op; + $this->accounts[] = $account; + } + + // Create an admin account (returns TRUE for all revision permissions). + $admin_account = $this->drupalCreateUser(array('access content', 'administer nodes')); + $admin_account->is_admin = TRUE; + $this->accounts['admin'] = $admin_account; + + // Create a normal account (returns FALSE for all revision permissions). + $normal_account = $this->drupalCreateUser(); + $normal_account->op = FALSE; + $this->accounts[] = $normal_account; + } + + /** + * Tests the entity_access() function for revisions. + */ + function testNodeRevisionAccess() { + // $node_revisions[1] won't be the latest revision. + $revision = $this->node_revisions[1]; + + $parameters = array( + 'op' => array_keys($this->map), + 'account' => $this->accounts, + ); + + $permutations = $this->generatePermutations($parameters); + $entity_type = 'node'; + foreach ($permutations as $case) { + if (!empty($case['account']->is_admin) || $case['op'] == $case['account']->op) { + $access = entity_access($case['op'], $entity_type, $revision, $case['account']); + $this->assertTrue($access, "{$this->map[$case['op']]} granted on $entity_type."); + } + else { + $access = entity_access($case['op'], $entity_type, $revision, $case['account']); + $this->assertFalse($access, "{$this->map[$case['op']]} NOT granted on $entity_type."); + } + } + } +} + +/** + * Tests basic entity_access() functionality for taxonomy terms. + */ +class EntityMetadataTaxonomyAccessTestCase extends EntityWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Entity Metadata Taxonomy Access', + 'description' => 'Test entity_access() for taxonomy terms', + 'group' => 'Entity API', + ); + } + + /** + * Asserts entity_access() correctly grants or denies access. + */ + function assertTaxonomyMetadataAccess($ops, $term, $account) { + foreach ($ops as $op => $result) { + $msg = t("entity_access() returns @result with operation '@op'.", array('@result' => $result ? 'TRUE' : 'FALSE', '@op' => $op)); + $access = entity_access($op, 'taxonomy_term', $term, $account); + $this->assertEqual($result, $access, $msg); + } + } + + /** + * @inheritdoc + */ + function setUp() { + parent::setUp('entity', 'taxonomy'); + // Clear permissions for authenticated users. + db_delete('role_permission') + ->condition('rid', DRUPAL_AUTHENTICATED_RID) + ->execute(); + } + + /** + * Runs basic tests for entity_access() function. + */ + function testTaxonomyMetadataAccess() { + $vocab = $this->createVocabulary(); + $term = entity_property_values_create_entity('taxonomy_term', array( + 'name' => $this->randomName(), + 'vocabulary' => $vocab, + ))->save()->value(); + // Clear permissions static cache to get new taxonomy permissions. + drupal_static_reset('checkPermissions'); + + // Check assignment of view permissions. + $user1 = $this->drupalCreateUser(array('access content')); + $this->assertTaxonomyMetadataAccess(array('create' => FALSE, 'view' => TRUE, 'update' => FALSE, 'delete' => FALSE), $term, $user1); + + // Check assignment of edit permissions. + $user2 = $this->drupalCreateUser(array('edit terms in ' . $vocab->vid)); + $this->assertTaxonomyMetadataAccess(array('create' => FALSE, 'view' => FALSE, 'update' => TRUE, 'delete' => FALSE), $term, $user2); + + // Check assignment of delete permissions. + $user3 = $this->drupalCreateUser(array('delete terms in ' . $vocab->vid)); + $this->assertTaxonomyMetadataAccess(array('create' => FALSE, 'view' => FALSE, 'update' => FALSE, 'delete' => TRUE), $term, $user3); + + // Check assignment of view, edit, delete permissions. + $user4 = $this->drupalCreateUser(array('access content', 'edit terms in ' . $vocab->vid, 'delete terms in ' . $vocab->vid)); + $this->assertTaxonomyMetadataAccess(array('create' => FALSE, 'view' => TRUE, 'update' => TRUE, 'delete' => TRUE), $term, $user4); + + // Check assignment of administration permissions. + $user5 = $this->drupalCreateUser(array('administer taxonomy')); + $this->assertTaxonomyMetadataAccess(array('create' => TRUE, 'view' => TRUE, 'update' => TRUE, 'delete' => TRUE), $term, $user5); + } +} + /** * Tests provided entity property info of the core modules. */ @@ -1137,6 +1511,11 @@ class EntityMetadataIntegrationTestCase extends EntityWebTestCase { $this->assertTrue($wrapper->$name->value() === NULL, 'Property ' . check_plain($name) . ' is empty.'); } + protected function assertEmptyArray($wrapper, $name) { + $this->assertTrue(isset($wrapper->$name), 'Property ' . check_plain($name) . ' exists.'); + $this->assertTrue($wrapper->$name->value() === array(), 'Property ' . check_plain($name) . ' is an empty array.'); + } + protected function assertValue($wrapper, $key) { $this->assertTrue($wrapper->$key->value() !== NULL, check_plain($key) . ' property returned.'); $info = $wrapper->$key->info(); @@ -1151,18 +1530,26 @@ class EntityMetadataIntegrationTestCase extends EntityWebTestCase { */ function testBookModule() { $title = 'Book 1'; - $node = $this->drupalCreateNode(array('title' => $title, 'type' => 'book')); - $node2 = $this->drupalCreateNode(array('type' => 'book', 'book' => array('bid' => $node->nid))); + $node = $this->drupalCreateNode(array('title' => $title, 'type' => 'book', 'book' => array('bid' => 'new'))); + $book = array('bid' => $node->nid, 'plid' => $node->book['mlid']); + $node2 = $this->drupalCreateNode(array('type' => 'book', 'book' => $book)); $node3 = $this->drupalCreateNode(array('type' => 'page')); + $node4 = $this->drupalCreateNode(array('type' => 'book', 'book' => array('bid' => 0, 'plid' => -1))); // Test whether the properties work. $wrapper = entity_metadata_wrapper('node', $node2); - $this->assertEqual("Book 1", $wrapper->book->title->value(), "Book title returned."); + $this->assertEqual($title, $wrapper->book->title->value(), "Book title returned."); + $this->assertEqual(array($node->nid), $wrapper->book_ancestors->value(array('identifier' => TRUE)), "Book ancestors returned."); $this->assertEqual($node->nid, $wrapper->book->nid->value(), "Book id returned."); // Try using book properties for no book nodes. $wrapper = entity_metadata_wrapper('node', $node3); - $this->assertException($wrapper, 'book'); + $this->assertEmpty($wrapper, 'book'); + $this->assertEmptyArray($wrapper, 'book_ancestors'); + + // Test a book node which is not contained in a hierarchy. + $wrapper = entity_metadata_wrapper('node', $node4); + $this->assertEmptyArray($wrapper, 'book_ancestors'); } /** @@ -1171,11 +1558,11 @@ class EntityMetadataIntegrationTestCase extends EntityWebTestCase { function testComments() { $title = 'Node 1'; $node = $this->drupalCreateNode(array('title' => $title, 'type' => 'page')); - $account = $this->drupalCreateUser(); + $author = $this->drupalCreateUser(array('access comments', 'post comments', 'edit own comments')); $comment = (object)array( 'subject' => 'topic', 'nid' => $node->nid, - 'uid' => $account->uid, + 'uid' => $author->uid, 'cid' => FALSE, 'pid' => 0, 'homepage' => '', @@ -1191,6 +1578,71 @@ class EntityMetadataIntegrationTestCase extends EntityWebTestCase { } } $this->assertEmpty($wrapper, 'parent'); + + // Test comment entity access. + $admin_user = $this->drupalCreateUser(array('access comments', 'administer comments', 'access user profiles')); + // Also grant access to view user accounts to test the comment author + // property. + $unprivileged_user = $this->drupalCreateUser(array('access comments', 'access user profiles')); + // Published comments can be viewed and edited by the author. + $this->assertTrue($wrapper->access('view', $author), 'Comment author is allowed to view the published comment.'); + $this->assertTrue($wrapper->access('edit', $author), 'Comment author is allowed to edit the published comment.'); + // We cannot use $wrapper->access('delete') here because it only understands + // view and edit. + $this->assertFalse(entity_access('delete', 'comment', $comment, $author), 'Comment author is not allowed to delete the published comment.'); + + // Administrators can do anything with published comments. + $this->assertTrue($wrapper->access('view', $admin_user), 'Comment administrator is allowed to view the published comment.'); + $this->assertTrue($wrapper->access('edit', $admin_user), 'Comment administrator is allowed to edit the published comment.'); + $this->assertTrue(entity_access('delete', 'comment', $comment, $admin_user), 'Comment administrator is allowed to delete the published comment.'); + + // Unpriviledged users can only view the published comment. + $this->assertTrue($wrapper->access('view', $unprivileged_user), 'Unprivileged user is allowed to view the published comment.'); + $this->assertFalse($wrapper->access('edit', $unprivileged_user), 'Unprivileged user is not allowed to edit the published comment.'); + $this->assertFalse(entity_access('delete', 'comment', $comment, $unprivileged_user), 'Unprivileged user is not allowed to delete the published comment.'); + + // Test property view access. + $view_access = array('name', 'homepage', 'subject', 'created', 'author', 'node', 'parent', 'url', 'edit_url'); + foreach ($view_access as $property_name) { + $this->assertTrue($wrapper->{$property_name}->access('view', $unprivileged_user), "Unpriviledged user can view the $property_name property."); + } + + $view_denied = array('hostname', 'mail', 'status'); + foreach ($view_denied as $property_name) { + $this->assertFalse($wrapper->{$property_name}->access('view', $unprivileged_user), "Unpriviledged user can not view the $property_name property."); + $this->assertTrue($wrapper->{$property_name}->access('view', $admin_user), "Admin user can view the $property_name property."); + } + + // The author is allowed to edit the comment subject if they have the + // 'edit own comments' permission. + $this->assertTrue($wrapper->subject->access('edit', $author), "Author can edit the subject property."); + $this->assertFalse($wrapper->subject->access('edit', $unprivileged_user), "Unpriviledged user cannot edit the subject property."); + $this->assertTrue($wrapper->subject->access('edit', $admin_user), "Admin user can edit the subject property."); + + $edit_denied = array('hostname', 'mail', 'status', 'name', 'homepage', 'created', 'parent', 'node', 'author'); + foreach ($edit_denied as $property_name) { + $this->assertFalse($wrapper->{$property_name}->access('edit', $author), "Author cannot edit the $property_name property."); + $this->assertTrue($wrapper->{$property_name}->access('edit', $admin_user), "Admin user can edit the $property_name property."); + } + + // Test access to unpublished comments. + $comment->status = COMMENT_NOT_PUBLISHED; + comment_save($comment); + + // Unpublished comments cannot be accessed by the author. + $this->assertFalse($wrapper->access('view', $author), 'Comment author is not allowed to view the unpublished comment.'); + $this->assertFalse($wrapper->access('edit', $author), 'Comment author is not allowed to edit the unpublished comment.'); + $this->assertFalse(entity_access('delete', 'comment', $comment, $author), 'Comment author is not allowed to delete the unpublished comment.'); + + // Administrators can do anything with unpublished comments. + $this->assertTrue($wrapper->access('view', $admin_user), 'Comment administrator is allowed to view the unpublished comment.'); + $this->assertTrue($wrapper->access('edit', $admin_user), 'Comment administrator is allowed to edit the unpublished comment.'); + $this->assertTrue(entity_access('delete', 'comment', $comment, $admin_user), 'Comment administrator is allowed to delete the unpublished comment.'); + + // Unpriviledged users cannot access unpublished comments. + $this->assertFalse($wrapper->access('view', $unprivileged_user), 'Unprivileged user is not allowed to view the unpublished comment.'); + $this->assertFalse($wrapper->access('edit', $unprivileged_user), 'Unprivileged user is not allowed to edit the unpublished comment.'); + $this->assertFalse(entity_access('delete', 'comment', $comment, $unprivileged_user), 'Unprivileged user is not allowed to delete the unpublished comment.'); } /** @@ -1201,14 +1653,33 @@ class EntityMetadataIntegrationTestCase extends EntityWebTestCase { $node = $this->drupalCreateNode(array('title' => $title, 'type' => 'page')); $wrapper = entity_metadata_wrapper('node', $node); foreach ($wrapper as $key => $value) { - if ($key != 'book' && $key != 'source' && $key != 'last_view') { + if ($key != 'book' && $key != 'book_ancestors' && $key != 'source' && $key != 'last_view') { $this->assertValue($wrapper, $key); } } - $this->assertException($wrapper, 'book'); + $this->assertEmpty($wrapper, 'book'); + $this->assertEmptyArray($wrapper, 'book_ancestors'); $this->assertEmpty($wrapper, 'source'); $this->assertException($wrapper->source, 'title'); $this->assertEmpty($wrapper, 'last_view'); + + // Test statistics module integration access. + $unpriviledged_user = $this->drupalCreateUser(array('access content')); + $this->assertTrue($wrapper->access('view', $unpriviledged_user), 'Unpriviledged user can view the node.'); + $this->assertFalse($wrapper->access('edit', $unpriviledged_user), 'Unpriviledged user can not edit the node.'); + $count_access_user = $this->drupalCreateUser(array('view post access counter')); + $admin_user = $this->drupalCreateUser(array('access content', 'view post access counter', 'access statistics')); + + $this->assertFalse($wrapper->views->access('view', $unpriviledged_user), "Unpriviledged user cannot view the statistics counter property."); + $this->assertTrue($wrapper->views->access('view', $count_access_user), "Count access user can view the statistics counter property."); + $this->assertTrue($wrapper->views->access('view', $admin_user), "Admin user can view the statistics counter property."); + + $admin_properties = array('day_views', 'last_view'); + foreach ($admin_properties as $property_name) { + $this->assertFalse($wrapper->{$property_name}->access('view', $unpriviledged_user), "Unpriviledged user cannot view the $property_name property."); + $this->assertFalse($wrapper->{$property_name}->access('view', $count_access_user), "Count access user cannot view the $property_name property."); + $this->assertTrue($wrapper->{$property_name}->access('view', $admin_user), "Admin user can view the $property_name property."); + } } /** @@ -1303,13 +1774,44 @@ class EntityMetadataIntegrationTestCase extends EntityWebTestCase { * Test all properties of a user. */ function testUserProperties() { - $account = $this->drupalCreateUser(); + $account = $this->drupalCreateUser(array('access user profiles', 'change own username')); $account->login = REQUEST_TIME; $account->access = REQUEST_TIME; $wrapper = entity_metadata_wrapper('user', $account); foreach ($wrapper as $key => $value) { $this->assertValue($wrapper, $key); } + + // Test property view access. + $unpriviledged_user = $this->drupalCreateUser(array('access user profiles')); + $admin_user = $this->drupalCreateUser(array('administer users')); + $this->assertTrue($wrapper->access('view', $unpriviledged_user), 'Unpriviledged account can view the user.'); + $this->assertFalse($wrapper->access('edit', $unpriviledged_user), 'Unpriviledged account can not edit the user.'); + + $view_access = array('name', 'url', 'edit_url', 'created'); + foreach ($view_access as $property_name) { + $this->assertTrue($wrapper->{$property_name}->access('view', $unpriviledged_user), "Unpriviledged user can view the $property_name property."); + } + + $view_denied = array('mail', 'last_access', 'last_login', 'roles', 'status', 'theme'); + foreach ($view_denied as $property_name) { + $this->assertFalse($wrapper->{$property_name}->access('view', $unpriviledged_user), "Unpriviledged user can not view the $property_name property."); + $this->assertTrue($wrapper->{$property_name}->access('view', $admin_user), "Admin user can view the $property_name property."); + } + + // Test property edit access. + $edit_own_allowed = array('name', 'mail'); + foreach ($edit_own_allowed as $property_name) { + $this->assertTrue($wrapper->{$property_name}->access('edit', $account), "Account owner can edit the $property_name property."); + } + + $this->assertTrue($wrapper->roles->access('view', $account), "Account owner can view their own roles."); + + $edit_denied = array('last_access', 'last_login', 'created', 'roles', 'status', 'theme'); + foreach ($edit_denied as $property_name) { + $this->assertFalse($wrapper->{$property_name}->access('edit', $account), "Account owner cannot edit the $property_name property."); + $this->assertTrue($wrapper->{$property_name}->access('edit', $admin_user), "Admin user can edit the $property_name property."); + } } /** diff --git a/sites/all/modules/contrib/dev/entity/entity_token.info b/sites/all/modules/contrib/dev/entity/entity_token.info index ae218e0..c59c650 100644 --- a/sites/all/modules/contrib/dev/entity/entity_token.info +++ b/sites/all/modules/contrib/dev/entity/entity_token.info @@ -5,9 +5,9 @@ files[] = entity_token.tokens.inc files[] = entity_token.module dependencies[] = entity -; Information added by drupal.org packaging script on 2013-04-30 -version = "7.x-1.1" +; Information added by Drupal.org packaging script on 2018-02-14 +version = "7.x-1.9" core = "7.x" project = "entity" -datestamp = "1367338216" +datestamp = "1518620551" diff --git a/sites/all/modules/contrib/dev/entity/entity_token.tokens.inc b/sites/all/modules/contrib/dev/entity/entity_token.tokens.inc index f9ec7f7..77a170d 100644 --- a/sites/all/modules/contrib/dev/entity/entity_token.tokens.inc +++ b/sites/all/modules/contrib/dev/entity/entity_token.tokens.inc @@ -180,9 +180,11 @@ function entity_token_tokens($type, $tokens, array $data = array(), array $optio $wrapper = !isset($wrapper) ? _entity_token_wrap_data($type, $token_types[$type], $data[$type], $options) : $wrapper; $property_name = str_replace('-', '_', $name); try { - $replacement = _entity_token_get_token($wrapper->$property_name, $options); - if (isset($replacement)) { - $replacements[$original] = $replacement; + if (isset($wrapper->$property_name)) { + $replacement = _entity_token_get_token($wrapper->$property_name, $options); + if (isset($replacement)) { + $replacements[$original] = $replacement; + } } } catch (EntityMetadataWrapperException $e) { @@ -294,7 +296,7 @@ function _entity_token_wrap_data($token_type, $type, $data, $options) { */ function _entity_token_get_token($wrapper, $options) { - if ($wrapper->value() === NULL) { + if (!$wrapper || $wrapper->value() === NULL) { // Do not provide a replacement if there is no value. return NULL; } diff --git a/sites/all/modules/contrib/dev/entity/includes/entity.controller.inc b/sites/all/modules/contrib/dev/entity/includes/entity.controller.inc index 2f87010..5e86b52 100644 --- a/sites/all/modules/contrib/dev/entity/includes/entity.controller.inc +++ b/sites/all/modules/contrib/dev/entity/includes/entity.controller.inc @@ -107,7 +107,7 @@ interface EntityAPIControllerInterface extends DrupalEntityControllerInterface { * content language of the current request. * @param $page * (optional) If set will control if the entity is rendered: if TRUE - * the entity will be rendered without its title, so that it can be embeded + * the entity will be rendered without its title, so that it can be embedded * in another context. If FALSE the entity will be displayed with its title * in a mode suitable for lists. * If unset, the page mode will be enabled if the current path is the URI @@ -171,7 +171,7 @@ class EntityAPIController extends DrupalDefaultEntityController implements Entit if ($this->revisionKey) { // Compare revision id of the base and revision table, if equal then this // is the default revision. - $query->addExpression('base.' . $this->revisionKey . ' = revision.' . $this->revisionKey, $this->defaultRevisionKey); + $query->addExpression('CASE WHEN base.' . $this->revisionKey . ' = revision.' . $this->revisionKey . ' THEN 1 ELSE 0 END', $this->defaultRevisionKey); } return $query; } @@ -373,10 +373,7 @@ class EntityAPIController extends DrupalDefaultEntityController implements Entit // Do nothing, in case invalid or no ids have been passed. return; } - // This transaction causes troubles on MySQL, see - // http://drupal.org/node/1007830. So we deactivate this by default until - // is shipped in a point release. - // $transaction = isset($transaction) ? $transaction : db_transaction(); + $transaction = isset($transaction) ? $transaction : db_transaction(); try { $ids = array_keys($entities); @@ -400,9 +397,7 @@ class EntityAPIController extends DrupalDefaultEntityController implements Entit db_ignore_slave(); } catch (Exception $e) { - if (isset($transaction)) { - $transaction->rollback(); - } + $transaction->rollback(); watchdog_exception($this->entityType, $e); throw $e; } @@ -587,6 +582,30 @@ class EntityAPIController extends DrupalDefaultEntityController implements Entit $entity->content = $content; $langcode = isset($langcode) ? $langcode : $GLOBALS['language_content']->language; + // Allow modules to change the view mode. + $context = array( + 'entity_type' => $this->entityType, + 'entity' => $entity, + 'langcode' => $langcode, + ); + drupal_alter('entity_view_mode', $view_mode, $context); + // Make sure the used view-mode gets stored. + $entity->content += array('#view_mode' => $view_mode); + + // By default add in properties for all defined extra fields. + if ($extra_field_controller = entity_get_extra_fields_controller($this->entityType)) { + $wrapper = entity_metadata_wrapper($this->entityType, $entity); + $extra = $extra_field_controller->fieldExtraFields(); + $type_extra = &$extra[$this->entityType][$this->entityType]['display']; + $bundle_extra = &$extra[$this->entityType][$wrapper->getBundle()]['display']; + + foreach ($wrapper as $name => $property) { + if (isset($type_extra[$name]) || isset($bundle_extra[$name])) { + $this->renderEntityProperty($wrapper, $name, $property, $view_mode, $langcode, $entity->content); + } + } + } + // Add in fields. if (!empty($this->entityInfo['fieldable'])) { // Perform the preparation tasks if they have not been performed yet. @@ -608,6 +627,24 @@ class EntityAPIController extends DrupalDefaultEntityController implements Entit return $build; } + /** + * Renders a single entity property. + */ + protected function renderEntityProperty($wrapper, $name, $property, $view_mode, $langcode, &$content) { + $info = $property->info(); + + $content[$name] = array( + '#label_hidden' => FALSE, + '#label' => $info['label'], + '#entity_wrapped' => $wrapper, + '#theme' => 'entity_property', + '#property_name' => $name, + '#access' => $property->access('view'), + '#entity_type' => $this->entityType, + ); + $content['#attached']['css']['entity.theme'] = drupal_get_path('module', 'entity') . '/theme/entity.theme.css'; + } + /** * Implements EntityAPIControllerInterface. */ diff --git a/sites/all/modules/contrib/dev/entity/includes/entity.inc b/sites/all/modules/contrib/dev/entity/includes/entity.inc index 2fe9997..2f504f3 100644 --- a/sites/all/modules/contrib/dev/entity/includes/entity.inc +++ b/sites/all/modules/contrib/dev/entity/includes/entity.inc @@ -5,6 +5,168 @@ * Provides a base class for entities. */ +/** + * Interface for class based entities. + */ +interface EntityInterface { + + /** + * Returns the internal, numeric identifier. + * + * Returns the numeric identifier, even if the entity type has specified a + * name key. In the latter case, the numeric identifier is supposed to be used + * when dealing generically with entities or internally to refer to an entity, + * i.e. in a relational database. If unsure, use Entity:identifier(). + */ + public function internalIdentifier(); + + /** + * Returns the entity identifier, i.e. the entities name or numeric id. + * + * @return + * The identifier of the entity. If the entity type makes use of a name key, + * the name is returned, else the numeric id. + * + * @see entity_id() + */ + public function identifier(); + + /** + * Returns the info of the type of the entity. + * + * @see entity_get_info() + */ + public function entityInfo(); + + /** + * Returns the type of the entity. + */ + public function entityType(); + + /** + * Returns the bundle of the entity. + * + * @return + * The bundle of the entity. Defaults to the entity type if the entity type + * does not make use of different bundles. + */ + public function bundle(); + + /** + * Returns the EntityMetadataWrapper of the entity. + * + * @return EntityDrupalWrapper + * An EntityMetadataWrapper containing the entity. + */ + public function wrapper(); + + /** + * Returns the label of the entity. + * + * Modules may alter the label by specifying another 'label callback' using + * hook_entity_info_alter(). + * + * @see entity_label() + */ + public function label(); + + /** + * Returns the uri of the entity just as entity_uri(). + * + * Modules may alter the uri by specifying another 'uri callback' using + * hook_entity_info_alter(). + * + * @see entity_uri() + */ + public function uri(); + + /** + * Checks if the entity has a certain exportable status. + * + * @param $status + * A status constant, i.e. one of ENTITY_CUSTOM, ENTITY_IN_CODE, + * ENTITY_OVERRIDDEN or ENTITY_FIXED. + * + * @return bool + * For exportable entities TRUE if the entity has the status, else FALSE. + * In case the entity is not exportable, NULL is returned. + * + * @see entity_has_status() + */ + public function hasStatus($status); + + /** + * Permanently saves the entity. + * + * @see entity_save() + */ + public function save(); + + /** + * Permanently deletes the entity. + * + * @see entity_delete() + */ + public function delete(); + + /** + * Exports the entity. + * + * @see entity_export() + */ + public function export($prefix = ''); + + /** + * Generate an array for rendering the entity. + * + * @see entity_view() + */ + public function view($view_mode = 'full', $langcode = NULL, $page = NULL); + + /** + * Builds a structured array representing the entity's content. + * + * @see entity_build_content() + */ + public function buildContent($view_mode = 'full', $langcode = NULL); + + /** + * Gets the raw, translated value of a property or field. + * + * Supports retrieving field translations as well as i18n string translations. + * + * Note that this returns raw data values, which might not reflect what + * has been declared for hook_entity_property_info() as no 'getter callbacks' + * are invoked or no referenced entities are loaded. For retrieving values + * reflecting the property info make use of entity metadata wrappers, see + * entity_metadata_wrapper(). + * + * @param $property + * The name of the property to return; e.g., 'title'. + * @param $langcode + * (optional) The language code of the language to which the value should + * be translated. If set to NULL, the default display language is being + * used. + * + * @return + * The raw, translated property value; or the raw, un-translated value if no + * translation is available. + * + * @todo Implement an analogous setTranslation() method for updating. + */ + public function getTranslation($property, $langcode = NULL); + + /** + * Checks whether the entity is the default revision. + * + * @return Boolean + * + * @see entity_revision_is_default() + */ + public function isDefaultRevision(); + +} + /** * A common class for entities. * @@ -25,17 +187,16 @@ * public $count = 0; * @endcode */ -class Entity { +class Entity implements EntityInterface { protected $entityType; protected $entityInfo; protected $idKey, $nameKey, $statusKey; protected $defaultLabel = FALSE; + protected $wrapper; /** - * Creates a new entity. - * - * @see entity_create() + * {@inheritdoc} */ public function __construct(array $values = array(), $entityType = NULL) { if (empty($entityType)) { @@ -56,68 +217,60 @@ class Entity { $this->entityInfo = entity_get_info($this->entityType); $this->idKey = $this->entityInfo['entity keys']['id']; $this->nameKey = isset($this->entityInfo['entity keys']['name']) ? $this->entityInfo['entity keys']['name'] : $this->idKey; - $this->statusKey = empty($info['entity keys']['status']) ? 'status' : $info['entity keys']['status']; + $this->statusKey = empty($this->entityInfo['entity keys']['status']) ? 'status' : $this->entityInfo['entity keys']['status']; } /** - * Returns the internal, numeric identifier. - * - * Returns the numeric identifier, even if the entity type has specified a - * name key. In the latter case, the numeric identifier is supposed to be used - * when dealing generically with entities or internally to refer to an entity, - * i.e. in a relational database. If unsure, use Entity:identifier(). + * {@inheritdoc} */ public function internalIdentifier() { return isset($this->{$this->idKey}) ? $this->{$this->idKey} : NULL; } /** - * Returns the entity identifier, i.e. the entities name or numeric id. - * - * @return - * The identifier of the entity. If the entity type makes use of a name key, - * the name is returned, else the numeric id. - * - * @see entity_id() + * {@inheritdoc} */ public function identifier() { return isset($this->{$this->nameKey}) ? $this->{$this->nameKey} : NULL; } /** - * Returns the info of the type of the entity. - * - * @see entity_get_info() + * {@inheritdoc} */ public function entityInfo() { return $this->entityInfo; } /** - * Returns the type of the entity. + * {@inheritdoc} */ public function entityType() { return $this->entityType; } /** - * Returns the bundle of the entity. - * - * @return - * The bundle of the entity. Defaults to the entity type if the entity type - * does not make use of different bundles. + * {@inheritdoc} */ public function bundle() { return !empty($this->entityInfo['entity keys']['bundle']) ? $this->{$this->entityInfo['entity keys']['bundle']} : $this->entityType; } /** - * Returns the label of the entity. - * - * Modules may alter the label by specifying another 'label callback' using - * hook_entity_info_alter(). - * - * @see entity_label() + * {@inheritdoc} + */ + public function wrapper() { + if (empty($this->wrapper)) { + $this->wrapper = entity_metadata_wrapper($this->entityType, $this); + } + elseif ($this->wrapper->value() !== $this) { + // Wrapper has been modified outside, so let's better create a new one. + $this->wrapper = entity_metadata_wrapper($this->entityType, $this); + } + return $this->wrapper; + } + + /** + * {@inheritdoc} */ public function label() { // If the default label flag is enabled, this is being invoked recursively. @@ -147,12 +300,7 @@ class Entity { } /** - * Returns the uri of the entity just as entity_uri(). - * - * Modules may alter the uri by specifying another 'uri callback' using - * hook_entity_info_alter(). - * - * @see entity_uri() + * {@inheritdoc} */ public function uri() { if (isset($this->entityInfo['uri callback']) && $this->entityInfo['uri callback'] == 'entity_class_uri') { @@ -170,17 +318,7 @@ class Entity { } /** - * Checks if the entity has a certain exportable status. - * - * @param $status - * A status constant, i.e. one of ENTITY_CUSTOM, ENTITY_IN_CODE, - * ENTITY_OVERRIDDEN or ENTITY_FIXED. - * - * @return - * For exportable entities TRUE if the entity has the status, else FALSE. - * In case the entity is not exportable, NULL is returned. - * - * @see entity_has_status() + * {@inheritdoc} */ public function hasStatus($status) { if (!empty($this->entityInfo['exportable'])) { @@ -189,18 +327,14 @@ class Entity { } /** - * Permanently saves the entity. - * - * @see entity_save() + * {@inheritdoc} */ public function save() { return entity_get_controller($this->entityType)->save($this); } /** - * Permanently deletes the entity. - * - * @see entity_delete() + * {@inheritdoc} */ public function delete() { $id = $this->identifier(); @@ -210,55 +344,28 @@ class Entity { } /** - * Exports the entity. - * - * @see entity_export() + * {@inheritdoc} */ public function export($prefix = '') { return entity_get_controller($this->entityType)->export($this, $prefix); } /** - * Generate an array for rendering the entity. - * - * @see entity_view() + * {@inheritdoc} */ public function view($view_mode = 'full', $langcode = NULL, $page = NULL) { return entity_get_controller($this->entityType)->view(array($this), $view_mode, $langcode, $page); } /** - * Builds a structured array representing the entity's content. - * - * @see entity_build_content() + * {@inheritdoc} */ public function buildContent($view_mode = 'full', $langcode = NULL) { return entity_get_controller($this->entityType)->buildContent($this, $view_mode, $langcode); } /** - * Gets the raw, translated value of a property or field. - * - * Supports retrieving field translations as well as i18n string translations. - * - * Note that this returns raw data values, which might not reflect what - * has been declared for hook_entity_property_info() as no 'getter callbacks' - * are invoked or no referenced entities are loaded. For retrieving values - * reflecting the property info make use of entity metadata wrappers, see - * entity_metadata_wrapper(). - * - * @param $property_name - * The name of the property to return; e.g., 'title'. - * @param $langcode - * (optional) The language code of the language to which the value should - * be translated. If set to NULL, the default display language is being - * used. - * - * @return - * The raw, translated property value; or the raw, un-translated value if no - * translation is available. - * - * @todo Implement an analogous setTranslation() method for updating. + * {@inheritdoc} */ public function getTranslation($property, $langcode = NULL) { $all_info = entity_get_all_property_info($this->entityType); @@ -278,11 +385,7 @@ class Entity { } /** - * Checks whether the entity is the default revision. - * - * @return Boolean - * - * @see entity_revision_is_default() + * {@inheritdoc} */ public function isDefaultRevision() { if (!empty($this->entityInfo['entity keys']['revision'])) { diff --git a/sites/all/modules/contrib/dev/entity/includes/entity.property.inc b/sites/all/modules/contrib/dev/entity/includes/entity.property.inc index 7b1dac7..e8714e6 100644 --- a/sites/all/modules/contrib/dev/entity/includes/entity.property.inc +++ b/sites/all/modules/contrib/dev/entity/includes/entity.property.inc @@ -69,7 +69,7 @@ function entity_property_info_defaults() { * (optiona) The entity type to return properties for. * * @return - * An array of info about properties. If the type is ommitted, all known + * An array of info about properties. If the type is omitted, all known * properties are returned. */ function entity_get_all_property_info($entity_type = NULL) { @@ -251,7 +251,11 @@ function entity_property_verify_data_type($data, $type) { return TRUE; } elseif (isset($info[$type]['entity keys']['name'])) { - return entity_property_verify_data_type($data, 'token'); + // Read the data type of the name key from the metadata if available. + $key = $info[$type]['entity keys']['name']; + $property_info = entity_get_property_info($type); + $property_type = isset($property_info['properties'][$key]['type']) ? $property_info['properties'][$key]['type'] : 'token'; + return entity_property_verify_data_type($data, $property_type); } return entity_property_verify_data_type($data, empty($info[$type]['fieldable']) ? 'text' : 'integer'); } @@ -392,7 +396,12 @@ function entity_property_verbatim_get($data, array $options, $name, $type, $info */ function entity_property_verbatim_date_get($data, array $options, $name, $type, $info) { $name = isset($info['schema field']) ? $info['schema field'] : $name; - return is_numeric($data[$name]) ? $data[$name] : strtotime($data[$name], REQUEST_TIME); + if (is_array($data) || (is_object($data) && $data instanceof ArrayAccess)) { + return is_numeric($data[$name]) ? $data[$name] : strtotime($data[$name], REQUEST_TIME); + } + elseif (is_object($data)) { + return is_numeric($data->$name) ? $data->$name : strtotime($data->$name, REQUEST_TIME); + } } /** @@ -503,6 +512,8 @@ function entity_property_text_formatted_info() { 'label' => t('Text format'), 'options list' => 'entity_metadata_field_text_formats', 'getter callback' => 'entity_property_verbatim_get', + 'setter callback' => 'entity_property_verbatim_set', + 'setter permissions' => 'administer filters', ), ); } diff --git a/sites/all/modules/contrib/dev/entity/includes/entity.ui.inc b/sites/all/modules/contrib/dev/entity/includes/entity.ui.inc index a449b25..24e3c2b 100644 --- a/sites/all/modules/contrib/dev/entity/includes/entity.ui.inc +++ b/sites/all/modules/contrib/dev/entity/includes/entity.ui.inc @@ -6,12 +6,16 @@ */ /** - * Default controller for providing UI. + * Default UI controller providing admin UI. + * + * This controller suites best for managing configuration entities. + * For a controller suiting content entities, see EntityContentUIController. */ class EntityDefaultUIController { protected $entityType; protected $entityInfo, $path; + protected $id_count; /** * Defines the number of entries to show per page in overview table. @@ -30,7 +34,8 @@ class EntityDefaultUIController { */ public function hook_menu() { $items = array(); - $id_count = count(explode('/', $this->path)); + // Set this on the object so classes that extend hook_menu() can use it. + $this->id_count = count(explode('/', $this->path)); $wildcard = isset($this->entityInfo['admin ui']['menu wildcard']) ? $this->entityInfo['admin ui']['menu wildcard'] : '%entity_object'; $plural_label = isset($this->entityInfo['plural label']) ? $this->entityInfo['plural label'] : $this->entityInfo['label'] . 's'; @@ -60,12 +65,12 @@ class EntityDefaultUIController { $items[$this->path . '/manage/' . $wildcard] = array( 'title' => 'Edit', 'title callback' => 'entity_label', - 'title arguments' => array($this->entityType, $id_count + 1), + 'title arguments' => array($this->entityType, $this->id_count + 1), 'page callback' => 'entity_ui_get_form', - 'page arguments' => array($this->entityType, $id_count + 1), + 'page arguments' => array($this->entityType, $this->id_count + 1), 'load arguments' => array($this->entityType), 'access callback' => 'entity_access', - 'access arguments' => array('update', $this->entityType, $id_count + 1), + 'access arguments' => array('update', $this->entityType, $this->id_count + 1), ); $items[$this->path . '/manage/' . $wildcard . '/edit'] = array( 'title' => 'Edit', @@ -77,7 +82,7 @@ class EntityDefaultUIController { $items[$this->path . '/manage/' . $wildcard . '/clone'] = array( 'title' => 'Clone', 'page callback' => 'entity_ui_get_form', - 'page arguments' => array($this->entityType, $id_count + 1, 'clone'), + 'page arguments' => array($this->entityType, $this->id_count + 1, 'clone'), 'load arguments' => array($this->entityType), 'access callback' => 'entity_access', 'access arguments' => array('create', $this->entityType), @@ -85,10 +90,10 @@ class EntityDefaultUIController { // Menu item for operations like revert and delete. $items[$this->path . '/manage/' . $wildcard . '/%'] = array( 'page callback' => 'drupal_get_form', - 'page arguments' => array($this->entityType . '_operation_form', $this->entityType, $id_count + 1, $id_count + 2), + 'page arguments' => array($this->entityType . '_operation_form', $this->entityType, $this->id_count + 1, $this->id_count + 2), 'load arguments' => array($this->entityType), 'access callback' => 'entity_access', - 'access arguments' => array('delete', $this->entityType, $id_count + 1), + 'access arguments' => array('delete', $this->entityType, $this->id_count + 1), 'file' => 'includes/entity.ui.inc', ); @@ -122,8 +127,8 @@ class EntityDefaultUIController { * Use per bundle form ids if possible, such that easy per bundle alterations * are supported too. * - * Note that for performance reasons, this method is only invoked for forms, - * which receive the entity_type as first argument. Thus any forms added, must + * Note that for performance reasons, this method is invoked only for forms + * which receive the entity_type as first argument. Thus any forms added must * follow that pattern. * * @see entity_forms() @@ -492,6 +497,128 @@ class EntityDefaultUIController { } } +/** + * UI controller providing UI for content entities. + * + * For a controller providing UI for bundleable content entities, see + * EntityBundleableUIController. + * For a controller providing admin UI for configuration entities, see + * EntityDefaultUIController. + */ +class EntityContentUIController extends EntityDefaultUIController { + + /** + * Provides definitions for implementing hook_menu(). + */ + public function hook_menu() { + $items = parent::hook_menu(); + $wildcard = isset($this->entityInfo['admin ui']['menu wildcard']) ? $this->entityInfo['admin ui']['menu wildcard'] : '%entity_object'; + + // Unset the manage entity path, as the provided UI is for admin entities. + unset($items[$this->path]); + + $defaults = array( + 'file' => $this->entityInfo['admin ui']['file'], + 'file path' => isset($this->entityInfo['admin ui']['file path']) ? $this->entityInfo['admin ui']['file path'] : drupal_get_path('module', $this->entityInfo['module']), + ); + + // Add view, edit and delete menu items for content entities. + $items[$this->path . '/' . $wildcard] = array( + 'title callback' => 'entity_ui_get_page_title', + 'title arguments' => array('view', $this->entityType, $this->id_count), + 'page callback' => 'entity_ui_entity_page_view', + 'page arguments' => array($this->id_count), + 'load arguments' => array($this->entityType), + 'access callback' => 'entity_access', + 'access arguments' => array('view', $this->entityType, $this->id_count), + ) + $defaults; + $items[$this->path . '/' . $wildcard . '/view'] = array( + 'title' => 'View', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'load arguments' => array($this->entityType), + 'weight' => -10, + ) + $defaults; + $items[$this->path . '/' . $wildcard . '/edit'] = array( + 'page callback' => 'entity_ui_get_form', + 'page arguments' => array($this->entityType, $this->id_count), + 'load arguments' => array($this->entityType), + 'access callback' => 'entity_access', + 'access arguments' => array('edit', $this->entityType, $this->id_count), + 'title' => 'Edit', + 'type' => MENU_LOCAL_TASK, + 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE, + ) + $defaults; + $items[$this->path . '/' . $wildcard . '/delete'] = array( + 'page callback' => 'drupal_get_form', + 'page arguments' => array($this->entityType . '_operation_form', $this->entityType, $this->id_count, 'delete'), + 'load arguments' => array($this->entityType), + 'access callback' => 'entity_access', + 'access arguments' => array('delete', $this->entityType, $this->id_count), + 'title' => 'Delete', + 'type' => MENU_LOCAL_TASK, + 'context' => MENU_CONTEXT_INLINE, + 'file' => $this->entityInfo['admin ui']['file'], + 'file path' => isset($this->entityInfo['admin ui']['file path']) ? $this->entityInfo['admin ui']['file path'] : drupal_get_path('module', $this->entityInfo['module']), + ) + $defaults; + + return $items; + } + + /** + * Operation form submit callback. + */ + public function operationFormSubmit($form, &$form_state) { + parent::operationFormSubmit($form, $form_state); + // The manage entity path is unset for the content entity UI. + $form_state['redirect'] = ''; + } +} + +/** + * UI controller providing UI for bundleable content entities. + * + * Adds a bundle selection page to the entity/add path, analogously to the + * node/add path. + */ +class EntityBundleableUIController extends EntityContentUIController { + + /** + * Provides definitions for implementing hook_menu(). + */ + public function hook_menu() { + $items = parent::hook_menu(); + + // Extend the 'add' path. + $items[$this->path . '/add'] = array( + 'title callback' => 'entity_ui_get_action_title', + 'title arguments' => array('add', $this->entityType), + 'page callback' => 'entity_ui_bundle_add_page', + 'page arguments' => array($this->entityType), + 'access callback' => 'entity_access', + 'access arguments' => array('create', $this->entityType), + 'type' => MENU_LOCAL_ACTION, + ); + $items[$this->path . '/add/%'] = array( + 'title callback' => 'entity_ui_get_action_title', + 'title arguments' => array('add', $this->entityType, $this->id_count + 1), + 'page callback' => 'entity_ui_get_bundle_add_form', + 'page arguments' => array($this->entityType, $this->id_count + 1), + 'access callback' => 'entity_access', + 'access arguments' => array('create', $this->entityType), + ); + + if (!empty($this->entityInfo['admin ui']['file'])) { + // Add in the include file for the entity form. + foreach (array('/add', '/add/%') as $path_end) { + $items[$this->path . $path_end]['file'] = $this->entityInfo['admin ui']['file']; + $items[$this->path . $path_end]['file path'] = isset($this->entityInfo['admin ui']['file path']) ? $this->entityInfo['admin ui']['file path'] : drupal_get_path('module', $this->entityInfo['module']); + } + } + + return $items; + } +} + /** * Form builder function for the overview form. * @@ -518,7 +645,7 @@ function entity_ui_operation_form($form, &$form_state, $entity_type, $entity, $o */ function entity_ui_main_form_defaults($form, &$form_state, $entity = NULL, $op = NULL) { // Now equals entity_ui_form_defaults() but is still here to keep backward - // compatability. + // compatibility. return entity_ui_form_defaults($form, $form_state, $form_state['entity_type'], $entity, $op); } @@ -597,39 +724,6 @@ function entity_ui_controller_form_submit($form, &$form_state) { entity_ui_controller($form_state['entity_type'])->$method($form, $form_state); } -/** - * Gets the page title for the passed operation. - */ -function entity_ui_get_page_title($op, $entity_type, $entity = NULL) { - $label = entity_label($entity_type, $entity); - switch ($op) { - case 'edit': - return t('Edit @label', array('@label' => $label)); - case 'clone': - return t('Clone @label', array('@label' => $label)); - case 'revert': - return t('Revert @label', array('@label' => $label)); - case 'delete': - return t('Delete @label', array('@label' => $label)); - case 'export': - return t('Export @label', array('@label' => $label)); - } - return entity_ui_get_action_title($op, $entity_type); -} - -/** - * Gets the page/menu title for local action operations. - */ -function entity_ui_get_action_title($op, $entity_type) { - $info = entity_get_info($entity_type); - switch ($op) { - case 'add': - return t('Add @entity_type', array('@entity_type' => drupal_strtolower($info['label']))); - case 'import': - return t('Import @entity_type', array('@entity_type' => drupal_strtolower($info['label']))); - } -} - /** * Submit builder for the main entity form, which extracts the form values and updates the entity. * @@ -666,7 +760,8 @@ function entity_ui_validate_machine_name($element, &$form_state) { function theme_entity_ui_overview_item($variables) { $output = $variables['url'] ? l($variables['label'], $variables['url']['path'], $variables['url']['options']) : check_plain($variables['label']); if ($variables['name']) { - $output .= ' (' . t('Machine name') . ': ' . check_plain($variables['name']) . ')'; + $output .= ' (' . t('Machine name') . ': ' . check_plain($variables['name']) . ')'; } return $output; } + diff --git a/sites/all/modules/contrib/dev/entity/includes/entity.wrapper.inc b/sites/all/modules/contrib/dev/entity/includes/entity.wrapper.inc index 4ee1c3a..860d2c3 100644 --- a/sites/all/modules/contrib/dev/entity/includes/entity.wrapper.inc +++ b/sites/all/modules/contrib/dev/entity/includes/entity.wrapper.inc @@ -119,7 +119,11 @@ abstract class EntityMetadataWrapper { */ public function set($value) { if (!$this->validate($value)) { - throw new EntityMetadataWrapperException('Invalid data value given. Be sure it matches the required data type and format.'); + throw new EntityMetadataWrapperException(t('Invalid data value given. Be sure it matches the required data type and format. Value at !location: !value.', array( + // An exception's message is output through check_plain(). + '!value' => is_array($value) || is_object($value) ? var_export($value, TRUE) : $value, + '!location' => $this->debugIdentifierLocation(), + ))); } $this->clear(); $this->data = $value; @@ -228,13 +232,24 @@ abstract class EntityMetadataWrapper { * If there is no access information for this property, TRUE is returned. */ public function access($op, $account = NULL) { - if (empty($this->info['parent']) && $this instanceof EntityDrupalWrapper) { - // If there is no parent just incorporate entity based access. - return $this->entityAccess($op == 'edit' ? 'update' : 'view', $account); - } return !empty($this->info['parent']) ? $this->info['parent']->propertyAccess($this->info['name'], $op, $account) : TRUE; } + /** + * Returns a string to use to identify this wrapper in error messages. + * + * @return + * A string that identifies this wrapper and its chain of ancestors, of the + * form 'grandparentidentifier->parentidentifier->identifier'. + */ + public function debugIdentifierLocation() { + $debug = $this->info['name']; + if (isset($this->info['parent'])) { + $debug = $this->info['parent']->debugIdentifierLocation() . '->' . $debug; + } + return $debug; + } + /** * Prepare for serializiation. */ @@ -500,19 +515,15 @@ class EntityStructureWrapper extends EntityMetadataWrapper implements IteratorAg protected function propertyAccess($name, $op, $account = NULL) { $info = $this->getPropertyInfo($name); - // If the property should be accessed and it's an entity, make sure the user - // is allowed to view that entity. - if ($op == 'view' && $this->$name instanceof EntityDrupalWrapper && !$this->$name->entityAccess($op, $account)) { - return FALSE; - } - // If a property should be edited and this is an entity, make sure the user - // has update access for this entity. + + // If a property should be edited and this is part of an entity, make sure + // the user has update access for this entity. if ($op == 'edit') { $entity = $this; while (!($entity instanceof EntityDrupalWrapper) && isset($entity->info['parent'])) { $entity = $entity->info['parent']; } - if ($entity instanceof EntityDrupalWrapper && !$entity->entityAccess('update', $account)) { + if ($entity instanceof EntityDrupalWrapper && $entity->entityAccess('update', $account) === FALSE) { return FALSE; } } @@ -523,6 +534,7 @@ class EntityStructureWrapper extends EntityMetadataWrapper implements IteratorAg elseif ($op == 'edit' && isset($info['setter permission'])) { return user_access($info['setter permission'], $account); } + // If access is unknown, we return TRUE. return TRUE; } @@ -662,7 +674,7 @@ class EntityDrupalWrapper extends EntityStructureWrapper { $this->bundle = $this->type; } // Detect the bundle if not set yet and add in properties from the bundle. - elseif (!$this->bundle && !empty($this->entityInfo['fieldable']) && $load && $this->dataAvailable()) { + elseif (!$this->bundle && $load && $this->dataAvailable()) { try { if ($entity = $this->value()) { list($id, $vid, $bundle) = entity_extract_ids($this->type, $entity); @@ -741,7 +753,11 @@ class EntityDrupalWrapper extends EntityStructureWrapper { */ public function set($value) { if (!$this->validate($value)) { - throw new EntityMetadataWrapperException('Invalid data value given. Be sure it matches the required data type and format.'); + throw new EntityMetadataWrapperException(t('Invalid data value given. Be sure it matches the required data type and format. Value at !location: !value.', array( + // An exception's message is output through check_plain(). + '!value' => is_array($value) || is_object($value) ? var_export($value, TRUE) : $value, + '!location' => $this->debugIdentifierLocation(), + ))); } if ($this->info['type'] == 'entity' && $value === $this) { // Nothing to do. @@ -766,7 +782,7 @@ class EntityDrupalWrapper extends EntityStructureWrapper { elseif ($this->id === FALSE && !$this->data) { $this->updateParent(NULL); } - elseif ($previous_id != $this->id) { + elseif ($previous_id !== $this->id) { $this->updateParent($this->id); } return $this; @@ -804,6 +820,39 @@ class EntityDrupalWrapper extends EntityStructureWrapper { return $this->type; } + /** + * {@inheritdoc} + * + * Note that this method checks property access, but can be used for checking + * entity access *only* if the wrapper is not a property (i.e. has no parent + * wrapper). + * To be safe, better use EntityDrupalWrapper::entityAccess() for checking + * entity access. + */ + public function access($op, $account = NULL) { + if (!empty($this->info['parent'])) { + // If this is a property, make sure the user is able to view the + // currently referenced entity also. + if ($this->entityAccess('view', $account) === FALSE) { + return FALSE; + } + if (parent::access($op, $account) === FALSE) { + return FALSE; + } + // If access is unknown, we return TRUE. + return TRUE; + } + else { + // This is not a property, so fallback on entity access. + if ($op == 'edit') { + // If the operation is "edit" determine if its actually a "create" for + // new un-saved entities, or an "update" for existing ones. + return $this->entityAccess($this->getIdentifier() ? 'update' : 'create', $account); + } + return $this->entityAccess('view', $account); + } + } + /** * Checks whether the operation $op is allowed on the entity. * @@ -811,6 +860,12 @@ class EntityDrupalWrapper extends EntityStructureWrapper { */ public function entityAccess($op, $account = NULL) { $entity = $this->dataAvailable() ? $this->value() : NULL; + // The value() method could return FALSE on entities such as user 0, so we + // need to use NULL instead to conform to the expectations of + // entity_access(). + if ($entity === FALSE) { + $entity = NULL; + } return entity_access($op, $this->type, $entity, $account); } @@ -882,6 +937,27 @@ class EntityDrupalWrapper extends EntityStructureWrapper { } } + /** + * Returns a string to use to identify this wrapper in error messages. + */ + public function debugIdentifierLocation() { + // An entity wrapper can be at the top of the chain or a part of it. + if (isset($this->info['name'])) { + // This wrapper is part of a chain, eg in the position node->author. + // Return the name. + $debug = $this->info['name']; + } + else { + // This is a wrapper for an actual entity: return its type and id. + $debug = $this->info['type'] . '(' . $this->getIdentifier() . ')'; + } + + if (isset($this->info['parent'])) { + $debug = $this->info['parent']->debugIdentifierLocation() . '->' . $debug; + } + return $debug; + } + /** * Prepare for serializiation. */ @@ -1025,8 +1101,11 @@ class EntityListWrapper extends EntityMetadataWrapper implements IteratorAggrega // Support setting lists of fully loaded entities. if ($this->isEntityList && $values && is_object(reset($values))) { foreach ($values as $key => $value) { - list($id, $vid, $bundle) = entity_extract_ids($this->itemType, $value); - $values[$key] = $id; + // Ignore outdated NULL value references in lists of entities. + if (isset($value)) { + list($id, $vid, $bundle) = entity_extract_ids($this->itemType, $value); + $values[$key] = $id; + } } } return parent::set($values); @@ -1037,7 +1116,7 @@ class EntityListWrapper extends EntityMetadataWrapper implements IteratorAggrega */ public function getIterator() { // In case there is no data available, just iterate over the first item. - return new EntityMetadataWrapperIterator($this, $this->dataAvailable() ? array_keys(parent::value()) : array(0)); + return new EntityMetadataWrapperIterator($this, ($this->dataAvailable() && is_array(parent::value())) ? array_keys(parent::value()) : array(0)); } /** diff --git a/sites/all/modules/contrib/dev/entity/modules/book.info.inc b/sites/all/modules/contrib/dev/entity/modules/book.info.inc index 3803ec8..96629c3 100644 --- a/sites/all/modules/contrib/dev/entity/modules/book.info.inc +++ b/sites/all/modules/contrib/dev/entity/modules/book.info.inc @@ -20,4 +20,11 @@ function entity_metadata_book_entity_property_info_alter(&$info) { 'description' => t("If part of a book, the book to which this book page belongs."), 'getter callback' => 'entity_metadata_book_get_properties', ); -} \ No newline at end of file + $properties['book_ancestors'] = array( + 'label' => t("Book ancestors"), + 'type' => 'list', + 'computed' => TRUE, + 'description' => t("If part of a book, a list of all book pages upwards in the book hierarchy."), + 'getter callback' => 'entity_metadata_book_get_properties', + ); +} diff --git a/sites/all/modules/contrib/dev/entity/modules/callbacks.inc b/sites/all/modules/contrib/dev/entity/modules/callbacks.inc index 304f53f..ee156ab 100644 --- a/sites/all/modules/contrib/dev/entity/modules/callbacks.inc +++ b/sites/all/modules/contrib/dev/entity/modules/callbacks.inc @@ -20,10 +20,22 @@ function entity_metadata_entity_get_properties($entity, array $options, $name, $ * @see entity_metadata_book_entity_info_alter() */ function entity_metadata_book_get_properties($node, array $options, $name, $entity_type) { - if (!isset($node->book['bid'])) { - throw new EntityMetadataWrapperException('This node is no book page.'); + switch ($name) { + case 'book': + if (isset($node->book['bid'])) { + return $node->book['bid']; + } + return NULL; + + case 'book_ancestors': + $ancestors = array(); + while (!empty($node->book['plid']) && $node->book['plid'] != -1) { + $link = book_link_load($node->book['plid']); + array_unshift($ancestors, $link['nid']); + $node = node_load($link['nid']); + } + return $ancestors; } - return $node->book['bid']; } /** @@ -80,6 +92,12 @@ function entity_metadata_comment_get_node_properties($node, array $options, $nam case 'comment_count_new': return comment_num_new($node->nid); + + case 'comments': + $select = db_select('comment', 'c') + ->fields('c', array('cid')) + ->condition('c.nid', $node->nid); + return array_keys($select->execute()->fetchAllKeyed(0, 0)); } } @@ -125,6 +143,9 @@ function entity_metadata_node_get_properties($node, array $options, $name, $enti case 'edit_url': return url('node/' . $node->nid . '/edit', $options); + + case 'author': + return !empty($node->uid) ? $node->uid : drupal_anonymous_user(); } } @@ -191,6 +212,16 @@ function entity_metadata_statistics_node_get_properties($node, array $options, $ } } +/** + * Access callback for restricted node statistics properties. + */ +function entity_metadata_statistics_properties_access($op, $property, $entity = NULL, $account = NULL) { + if ($property == 'views' && user_access('view post access counter', $account)) { + return TRUE; + } + return user_access('access statistics', $account); +} + /** * Callback for getting site-wide properties. * @see entity_metadata_system_entity_info_alter() @@ -388,7 +419,7 @@ function entity_metadata_language_list() { $list = array(); $list[LANGUAGE_NONE] = t('Language neutral'); foreach (language_list() as $language) { - $list[$language->language] = $language->name; + $list[$language->language] = t($language->name); } return $list; } @@ -460,8 +491,15 @@ function entity_metadata_field_options_list($name, $info) { // No data available. $entity = NULL; } - $instance = $wrapper->getBundle() ? field_info_instance($wrapper->type(), $name, $wrapper->getBundle()) : NULL; - return (array) module_invoke($field['module'], 'options_list', $field, $instance, $wrapper->type(), $entity); + + // Support translating labels via i18n field. + if (module_exists('i18n_field') && ($translate = i18n_field_type_info($field['type'], 'translate_options'))) { + return $translate($field); + } + else { + $instance = $wrapper->getBundle() ? field_info_instance($wrapper->type(), $name, $wrapper->getBundle()) : NULL; + return (array) module_invoke($field['module'], 'options_list', $field, $instance, $wrapper->type(), $entity); + } } } @@ -612,23 +650,53 @@ function entity_metadata_field_file_validate_item($items, $context) { * * This function does not implement hook_node_access(), thus it may not be * called entity_metadata_node_access(). + * + * @see entity_access() + * + * @param $op + * The operation being performed. One of 'view', 'update', 'create' or + * 'delete'. + * @param $node + * A node to check access for. Must be a node object. Must have nid, + * except in the case of 'create' operations. + * @param $account + * The user to check for. Leave it to NULL to check for the global user. + * + * @throws EntityMalformedException + * + * @return boolean + * TRUE if access is allowed, FALSE otherwise. */ function entity_metadata_no_hook_node_access($op, $node = NULL, $account = NULL) { + // First deal with the case where a $node is provided. if (isset($node)) { + if (empty($node->vid) && in_array($op, array('create', 'update'))) { + // This is a new node or the original node. + if (isset($node->type)) { + $op = empty($node->nid) || !empty($node->is_new) ? 'create' : 'update'; + return node_access($op, $op == 'create' ? $node->type : $node, $account); + } + else { + throw new EntityMalformedException('Permission to create a node was requested but no node type was given.'); + } + } // If a non-default revision is given, incorporate revision access. $default_revision = node_load($node->nid); - if ($node->vid != $default_revision->vid) { - return _node_revision_access($node, $op); + if ($node->vid !== $default_revision->vid) { + return _node_revision_access($node, $op, $account); } else { return node_access($op, $node, $account); } } - // Is access to all nodes allowed? + // No node is provided. Check for access to all nodes. + if (user_access('bypass node access', $account)) { + return TRUE; + } if (!user_access('access content', $account)) { return FALSE; } - if (user_access('bypass node access', $account) || (!isset($account) && $op == 'view' && node_access_view_all_nodes())) { + if ($op == 'view' && node_access_view_all_nodes($account)) { return TRUE; } return FALSE; @@ -637,13 +705,14 @@ function entity_metadata_no_hook_node_access($op, $node = NULL, $account = NULL) /** * Access callback for the user entity. */ -function entity_metadata_user_access($op, $entity = NULL, $account = NULL, $entity_type) { +function entity_metadata_user_access($op, $entity = NULL, $account = NULL, $entity_type = NULL) { $account = isset($account) ? $account : $GLOBALS['user']; // Grant access to the users own user account and to the anonymous one. - if (isset($entity) && $op != 'delete' && (($entity->uid == $account->uid && $entity->uid) || (!$entity->uid && $op == 'view'))) { + if (isset($entity->uid) && $op != 'delete' && (($entity->uid == $account->uid && $entity->uid) || (!$entity->uid && $op == 'view'))) { return TRUE; } - if (user_access('administer users', $account) || user_access('access user profiles', $account) && $op == 'view' && $entity->status) { + if (user_access('administer users', $account) + || user_access('access user profiles', $account) && $op == 'view' && (empty($entity) || !empty($entity->status))) { return TRUE; } return FALSE; @@ -658,7 +727,7 @@ function entity_metadata_user_properties_access($op, $property, $entity = NULL, } $account = isset($account) ? $account : $GLOBALS['user']; // Flag to indicate if this user entity is the own user account. - $is_own_account = isset($entity) && $account->uid == $entity->uid; + $is_own_account = isset($entity->uid) && $account->uid == $entity->uid; switch ($property) { case 'name': // Allow view access to anyone with access to the entity. @@ -681,27 +750,83 @@ function entity_metadata_user_properties_access($op, $property, $entity = NULL, * Access callback for the comment entity. */ function entity_metadata_comment_access($op, $entity = NULL, $account = NULL) { - if (isset($entity) && !isset($account) && comment_access($op, $entity)) { + // When determining access to a comment, 'comment_access' does not take any + // access restrictions to the comment's associated node into account. If a + // comment has an associated node, the user must be able to view it in order + // to access the comment. + if (isset($entity->nid)) { + if (!entity_access('view', 'node', node_load($entity->nid), $account)) { + return FALSE; + } + } + + // Comment administrators are allowed to perform all operations on all + // comments. + if (user_access('administer comments', $account)) { return TRUE; } - if (user_access('administer comments', $account) || user_access('access comments', $account) && $op == 'view') { + + // Unpublished comments can never be accessed by non-admins. + if (isset($entity->status) && $entity->status == COMMENT_NOT_PUBLISHED) { + return FALSE; + } + + if (isset($entity) && $op == 'update') { + // Because 'comment_access' only checks the current user, we need to do our + // own access checking if an account was specified. + if (!isset($account)) { + return comment_access('edit', $entity); + } + else { + return $account->uid && $account->uid == $entity->uid && user_access('edit own comments', $account); + } + } + if (user_access('access comments', $account) && $op == 'view') { return TRUE; } return FALSE; } +/** + * Access callback for restricted comment properties. + */ +function entity_metadata_comment_properties_access($op, $property, $entity = NULL, $account = NULL) { + return user_access('administer comments', $account); +} + /** * Access callback for the taxonomy entities. */ -function entity_metadata_taxonomy_access($op, $entity = NULL, $account = NULL, $entity_type) { - if ($entity_type == 'taxonomy_vocabulary') { - return user_access('administer taxonomy', $account); - } - if (isset($entity) && $op == 'update' && !isset($account) && taxonomy_term_edit_access($entity)) { +function entity_metadata_taxonomy_access($op, $entity = NULL, $account = NULL, $entity_type = NULL) { + // If user has administer taxonomy permission then no further checks. + if (user_access('administer taxonomy', $account)) { return TRUE; } - if (user_access('administer taxonomy', $account) || user_access('access content', $account) && $op == 'view') { - return TRUE; + switch ($op) { + case "view": + if (user_access('access content', $account)) { + return TRUE; + } + break; + case "update": + if ($entity_type == 'taxonomy_term') { + return user_access("edit terms in $entity->vid", $account); + } + break; + case "create": + if ($entity_type == 'taxonomy_term') { + // Check for taxonomy_access_fix contrib module which adds additional + // permissions to create new terms in a given vocabulary. + if (function_exists('taxonomy_access_fix_access')) { + return taxonomy_access_fix_access('add terms', $entity->vocabulary_machine_name); + } + } + break; + case "delete": + if ($entity_type == 'taxonomy_term') { + return user_access("delete terms in $entity->vid", $account); + } + break; } return FALSE; } @@ -877,10 +1002,17 @@ function entity_metadata_form_comment($comment) { * Callback to get the form of a user account. */ function entity_metadata_form_user($account) { - // Pre-populate the form-state with the right form include. + // If $account->uid is set then we want a user edit form. + // Otherwise we want the user register form. + if (isset($account->uid)) { + $form_id = 'user_profile_form'; + form_load_include($form_state, 'inc', 'user', 'user.pages'); + } + else { + $form_id = 'user_register_form'; + } $form_state['build_info']['args'] = array($account); - form_load_include($form_state, 'inc', 'user', 'user.pages'); - return drupal_build_form('user_profile_form', $form_state); + return drupal_build_form($form_id, $form_state); } /** @@ -896,11 +1028,11 @@ function entity_metadata_form_taxonomy_term($term) { /** * Callback to get the form of a vocabulary. */ -function entity_metadata_form_taxonomy_vocabulary($term) { +function entity_metadata_form_taxonomy_vocabulary($vocab) { // Pre-populate the form-state with the right form include. - $form_state['build_info']['args'] = array($term); + $form_state['build_info']['args'] = array($vocab); form_load_include($form_state, 'inc', 'taxonomy', 'taxonomy.admin'); - return drupal_build_form('taxonomy_form_term', $form_state); + return drupal_build_form('taxonomy_form_vocabulary', $form_state); } /** @@ -956,3 +1088,12 @@ function entity_metadata_field_query($entity_type, $property, $value, $limit) { $result = $query->execute(); return !empty($result[$entity_type]) ? array_keys($result[$entity_type]) : array(); } + +/** + * Implements entity_uri() callback for file entities. + */ +function entity_metadata_uri_file($file) { + return array( + 'path' => file_create_url($file->uri), + ); +} diff --git a/sites/all/modules/contrib/dev/entity/modules/comment.info.inc b/sites/all/modules/contrib/dev/entity/modules/comment.info.inc index 4321b4c..663c09c 100644 --- a/sites/all/modules/contrib/dev/entity/modules/comment.info.inc +++ b/sites/all/modules/contrib/dev/entity/modules/comment.info.inc @@ -24,6 +24,7 @@ function entity_metadata_comment_entity_property_info() { $properties['hostname'] = array( 'label' => t("IP Address"), 'description' => t("The IP address of the computer the comment was posted from."), + 'access callback' => 'entity_metadata_comment_properties_access', 'schema field' => 'hostname', ); $properties['name'] = array( @@ -40,8 +41,8 @@ function entity_metadata_comment_entity_property_info() { 'description' => t("The email address left by the comment author."), 'getter callback' => 'entity_metadata_comment_get_properties', 'setter callback' => 'entity_property_verbatim_set', - 'setter permission' => 'administer comments', 'validation callback' => 'valid_email_address', + 'access callback' => 'entity_metadata_comment_properties_access', 'schema field' => 'mail', ); $properties['homepage'] = array( @@ -56,7 +57,6 @@ function entity_metadata_comment_entity_property_info() { 'label' => t("Subject"), 'description' => t("The subject of the comment."), 'setter callback' => 'entity_property_verbatim_set', - 'setter permission' => 'administer comments', 'sanitize' => 'filter_xss', 'required' => TRUE, 'schema field' => 'subject', @@ -88,6 +88,7 @@ function entity_metadata_comment_entity_property_info() { 'description' => t("The comment's parent, if comment threading is active."), 'type' => 'comment', 'getter callback' => 'entity_metadata_comment_get_properties', + 'setter permission' => 'administer comments', 'schema field' => 'pid', ); $properties['node'] = array( @@ -116,7 +117,7 @@ function entity_metadata_comment_entity_property_info() { // it is an integer, so we follow the schema definition. 'type' => 'integer', 'options list' => 'entity_metadata_status_options_list', - 'setter permission' => 'administer comments', + 'access callback' => 'entity_metadata_comment_properties_access', 'schema field' => 'status', ); return $info; @@ -136,6 +137,13 @@ function entity_metadata_comment_entity_property_info_alter(&$info) { 'setter permission' => 'administer comments', 'type' => 'integer', ); + $properties['comments'] = array( + 'label' => t("Comments"), + 'type' => 'list', + 'description' => t("The node comments."), + 'getter callback' => 'entity_metadata_comment_get_node_properties', + 'computed' => TRUE, + ); $properties['comment_count'] = array( 'label' => t("Comment count"), 'description' => t("The number of comments posted on a node."), diff --git a/sites/all/modules/contrib/dev/entity/modules/field.info.inc b/sites/all/modules/contrib/dev/entity/modules/field.info.inc index 9ef372b..aeea79a 100644 --- a/sites/all/modules/contrib/dev/entity/modules/field.info.inc +++ b/sites/all/modules/contrib/dev/entity/modules/field.info.inc @@ -51,7 +51,10 @@ function entity_metadata_field_default_property_callback(&$info, $entity_type, $ $property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$name]; $instance += array('property info' => array()); $property = $instance['property info'] + array( - 'label' => $instance['label'], + // Since the label will be exposed via hook_token_info() and it is not + // clearly defined if that should be sanitized already we prevent XSS + // right here (field labels are user provided text). + 'label' => filter_xss_admin($instance['label']), 'type' => $field_type['property_type'], 'description' => t('Field "@name".', array('@name' => $name)), 'getter callback' => 'entity_metadata_field_property_get', @@ -164,7 +167,7 @@ function entity_metadata_field_image_callback(&$info, $entity_type, $field, $ins if (empty($instance['settings']['alt_field'])) { unset($property['property info']['alt']); } - if (empty($field['settings']['title_field'])) { + if (empty($instance['settings']['title_field'])) { unset($property['property info']['title']); } } diff --git a/sites/all/modules/contrib/dev/entity/modules/node.info.inc b/sites/all/modules/contrib/dev/entity/modules/node.info.inc index e4c15b7..f146a7e 100644 --- a/sites/all/modules/contrib/dev/entity/modules/node.info.inc +++ b/sites/all/modules/contrib/dev/entity/modules/node.info.inc @@ -118,6 +118,7 @@ function entity_metadata_node_entity_property_info() { 'label' => t("Author"), 'type' => 'user', 'description' => t("The author of the node."), + 'getter callback' => 'entity_metadata_node_get_properties', 'setter callback' => 'entity_property_verbatim_set', 'setter permission' => 'administer nodes', 'required' => TRUE, @@ -162,4 +163,10 @@ function entity_metadata_node_entity_property_info_alter(&$info) { 'auto creation' => 'entity_property_create_array', 'field' => TRUE, ); + + // Make it a list if cardinality is not 1. + $field_body = field_info_field('body'); + if (isset($field_body) && $field_body['cardinality'] != 1) { + $info['node']['properties']['body']['type'] = 'list'; + } } diff --git a/sites/all/modules/contrib/dev/entity/modules/statistics.info.inc b/sites/all/modules/contrib/dev/entity/modules/statistics.info.inc index 6633eaa..cb3fb37 100644 --- a/sites/all/modules/contrib/dev/entity/modules/statistics.info.inc +++ b/sites/all/modules/contrib/dev/entity/modules/statistics.info.inc @@ -19,6 +19,7 @@ function entity_metadata_statistics_entity_property_info_alter(&$info) { 'type' => 'integer', 'getter callback' => 'entity_metadata_statistics_node_get_properties', 'computed' => TRUE, + 'access callback' => 'entity_metadata_statistics_properties_access', ); $properties['day_views'] = array( 'label' => t("Views today"), @@ -26,6 +27,7 @@ function entity_metadata_statistics_entity_property_info_alter(&$info) { 'type' => 'integer', 'getter callback' => 'entity_metadata_statistics_node_get_properties', 'computed' => TRUE, + 'access callback' => 'entity_metadata_statistics_properties_access', ); $properties['last_view'] = array( 'label' => t("Last view"), @@ -33,5 +35,6 @@ function entity_metadata_statistics_entity_property_info_alter(&$info) { 'type' => 'date', 'getter callback' => 'entity_metadata_statistics_node_get_properties', 'computed' => TRUE, + 'access callback' => 'entity_metadata_statistics_properties_access', ); } diff --git a/sites/all/modules/contrib/dev/entity/modules/user.info.inc b/sites/all/modules/contrib/dev/entity/modules/user.info.inc index 6a8e4b8..67a62b5 100644 --- a/sites/all/modules/contrib/dev/entity/modules/user.info.inc +++ b/sites/all/modules/contrib/dev/entity/modules/user.info.inc @@ -59,6 +59,7 @@ function entity_metadata_user_entity_property_info() { 'description' => t("The date the user last accessed the site."), 'getter callback' => 'entity_metadata_user_get_properties', 'type' => 'date', + 'access callback' => 'entity_metadata_user_properties_access', 'schema field' => 'access', ); $properties['last_login'] = array( @@ -66,6 +67,7 @@ function entity_metadata_user_entity_property_info() { 'description' => t("The date the user last logged in to the site."), 'getter callback' => 'entity_metadata_user_get_properties', 'type' => 'date', + 'access callback' => 'entity_metadata_user_properties_access', 'schema field' => 'login', ); $properties['created'] = array( @@ -73,6 +75,7 @@ function entity_metadata_user_entity_property_info() { 'description' => t("The date the user account was created."), 'type' => 'date', 'schema field' => 'created', + 'setter permission' => 'administer users', ); $properties['roles'] = array( 'label' => t("User roles"), @@ -80,7 +83,6 @@ function entity_metadata_user_entity_property_info() { 'type' => 'list', 'getter callback' => 'entity_metadata_user_get_properties', 'setter callback' => 'entity_metadata_user_set_properties', - 'setter permission' => 'administer users', 'options list' => 'entity_metadata_user_roles', 'access callback' => 'entity_metadata_user_properties_access', ); @@ -92,7 +94,7 @@ function entity_metadata_user_entity_property_info() { // it is an integer, so we follow the schema definition. 'type' => 'integer', 'options list' => 'entity_metadata_user_status_options_list', - 'setter permission' => 'administer users', + 'access callback' => 'entity_metadata_user_properties_access', 'schema field' => 'status', ); $properties['theme'] = array( diff --git a/sites/all/modules/contrib/dev/entity/tests/entity_feature.info b/sites/all/modules/contrib/dev/entity/tests/entity_feature.info index b5424a6..524fdfa 100644 --- a/sites/all/modules/contrib/dev/entity/tests/entity_feature.info +++ b/sites/all/modules/contrib/dev/entity/tests/entity_feature.info @@ -6,9 +6,9 @@ files[] = entity_feature.module dependencies[] = entity_test hidden = TRUE -; Information added by drupal.org packaging script on 2013-04-30 -version = "7.x-1.1" +; Information added by Drupal.org packaging script on 2018-02-14 +version = "7.x-1.9" core = "7.x" project = "entity" -datestamp = "1367338216" +datestamp = "1518620551" diff --git a/sites/all/modules/contrib/dev/entity/tests/entity_test.info b/sites/all/modules/contrib/dev/entity/tests/entity_test.info index d7b9773..b7cceab 100644 --- a/sites/all/modules/contrib/dev/entity/tests/entity_test.info +++ b/sites/all/modules/contrib/dev/entity/tests/entity_test.info @@ -7,9 +7,9 @@ files[] = entity_test.install dependencies[] = entity hidden = TRUE -; Information added by drupal.org packaging script on 2013-04-30 -version = "7.x-1.1" +; Information added by Drupal.org packaging script on 2018-02-14 +version = "7.x-1.9" core = "7.x" project = "entity" -datestamp = "1367338216" +datestamp = "1518620551" diff --git a/sites/all/modules/contrib/dev/entity/tests/entity_test.install b/sites/all/modules/contrib/dev/entity/tests/entity_test.install index 42ba4b6..a969323 100644 --- a/sites/all/modules/contrib/dev/entity/tests/entity_test.install +++ b/sites/all/modules/contrib/dev/entity/tests/entity_test.install @@ -51,8 +51,14 @@ function entity_test_schema() { 'uid' => array('uid'), ), 'foreign keys' => array( - 'uid' => array('users' => 'uid'), - 'name' => array('entity_test_types' => 'name'), + 'uid' => array( + 'table' => 'users', + 'columns' => array('uid' => 'uid') + ), + 'name' => array( + 'table' => 'entity_test_types', + 'columns' => array('name' => 'name') + ), ), 'primary key' => array('pid'), ); diff --git a/sites/all/modules/contrib/dev/entity/tests/entity_test.module b/sites/all/modules/contrib/dev/entity/tests/entity_test.module index 8c82afe..727797a 100644 --- a/sites/all/modules/contrib/dev/entity/tests/entity_test.module +++ b/sites/all/modules/contrib/dev/entity/tests/entity_test.module @@ -2,7 +2,7 @@ /** * @file - * Test moduel for the entity API. + * Test module for the entity API. */ /** @@ -267,8 +267,15 @@ function entity_test_entity_getter($node) { if (empty($node->entity)) { $node->entity = array('type' => 'user', 'id' => $node->uid); } + // We have to return the entity wrapped. - return entity_metadata_wrapper($node->entity['type'], $node->entity['id']); + // Special handling for anonymous user. + if ($node->entity['type'] === 'user' && empty($node->entity['id'])) { + return entity_metadata_wrapper('user', drupal_anonymous_user()); + } + else { + return entity_metadata_wrapper($node->entity['type'], $node->entity['id']); + } } /** diff --git a/sites/all/modules/contrib/dev/entity/tests/entity_test_i18n.info b/sites/all/modules/contrib/dev/entity/tests/entity_test_i18n.info index 1542e1f..4ca048a 100644 --- a/sites/all/modules/contrib/dev/entity/tests/entity_test_i18n.info +++ b/sites/all/modules/contrib/dev/entity/tests/entity_test_i18n.info @@ -5,9 +5,9 @@ dependencies[] = i18n_string package = Multilingual - Internationalization core = 7.x hidden = TRUE -; Information added by drupal.org packaging script on 2013-04-30 -version = "7.x-1.1" +; Information added by Drupal.org packaging script on 2018-02-14 +version = "7.x-1.9" core = "7.x" project = "entity" -datestamp = "1367338216" +datestamp = "1518620551" diff --git a/sites/all/modules/contrib/dev/entity/theme/entity.theme.css b/sites/all/modules/contrib/dev/entity/theme/entity.theme.css new file mode 100644 index 0000000..a57318d --- /dev/null +++ b/sites/all/modules/contrib/dev/entity/theme/entity.theme.css @@ -0,0 +1,4 @@ + +.entity-property-label { + font-weight: bold; +} diff --git a/sites/all/modules/contrib/dev/entity/theme/entity.theme.inc b/sites/all/modules/contrib/dev/entity/theme/entity.theme.inc new file mode 100644 index 0000000..fc0ba7c --- /dev/null +++ b/sites/all/modules/contrib/dev/entity/theme/entity.theme.inc @@ -0,0 +1,215 @@ +' . $variables['label'] . ': 
    '; + } + + // Render the content. + $content_suffix = ''; + if (!$variables['label_hidden'] || $variables['content_attributes']) { + $output .= ''; + $content_suffix = '
    '; + } + $output .= $variables['content'] . $content_suffix; + + // Render the top-level DIV. + return '' . $output . ''; +} + +/** + * Theme preprocess function for theme_entity_property(). + * + * @see theme_entity_property() + */ +function template_preprocess_entity_property(&$variables, $hook) { + $element = $variables['elements']; + + $variables += array( + 'theme_hook_suggestions' => array(), + 'attributes_array' => array(), + ); + // Generate variables from element properties. + foreach (array('label_hidden', 'label', 'property_name') as $name) { + $variables[$name] = check_plain($element['#' . $name]); + } + $variables['title_attributes_array']['class'][] = 'entity-property-label'; + $variables['attributes_array'] = array_merge($variables['attributes_array'], isset($element['#attributes']) ? $element['#attributes'] : array()); + + $variables['property_name_css'] = strtr($element['#property_name'], '_', '-'); + $variables['attributes_array']['class'][] = 'entity-property'; + $variables['attributes_array']['class'][] = 'entity-property-' . $variables['property_name_css']; + + // Add specific suggestions that can override the default implementation. + $variables['theme_hook_suggestions'] += array( + 'entity_property__' . $element['#property_name'], + 'entity_property__' . $element['#entity_type'] . '__' . $element['#property_name'], + ); + + // Populate the content with sensible defaults. + if (!isset($element['#content'])) { + $variables['content'] = entity_property_default_render_value_by_type($element['#entity_wrapped']->{$element['#property_name']}); + } + else { + $variables['content'] = $element['#content']; + } +} + +/** + * Renders a property using simple defaults based upon the property type. + * + * @return string + */ +function entity_property_default_render_value_by_type(EntityMetadataWrapper $property) { + // If there is an options list or entity label, render that by default. + if ($label = $property->label()) { + if ($property instanceof EntityDrupalWrapper && $uri = entity_uri($property->type(), $property->value())) { + return l($label, $uri['path'], $uri['options']); + } + else { + return check_plain($label); + } + } + switch ($property->type()) { + case 'boolean': + return $property->value() ? t('yes') : t('no'); + default: + return check_plain($property->value()); + } +} + +/** + * Theme process function for theme_entity_property(). + * + * Taken over from template_process_field() + * + * @see theme_entity_property() + */ +function template_process_entity_property(&$variables, $hook) { + $element = $variables['elements']; + // The default theme implementation is a function, so template_process() does + // not automatically run, so we need to flatten the classes and attributes + // here. For best performance, only call drupal_attributes() when needed, and + // note that template_preprocess_field() does not initialize the + // *_attributes_array variables. + $variables['attributes'] = empty($variables['attributes_array']) ? '' : drupal_attributes($variables['attributes_array']); + $variables['title_attributes'] = empty($variables['title_attributes_array']) ? '' : drupal_attributes($variables['title_attributes_array']); + $variables['content_attributes'] = empty($variables['content_attributes_array']) ? '' : drupal_attributes($variables['content_attributes_array']); +} + +/** + * Themes the exportable status of an entity. + */ +function theme_entity_status($variables) { + $status = $variables['status']; + $html = $variables['html']; + if (($status & ENTITY_FIXED) == ENTITY_FIXED) { + $label = t('Fixed'); + $help = t('The configuration is fixed and cannot be changed.'); + return $html ? "" . $label . "" : $label; + } + elseif (($status & ENTITY_OVERRIDDEN) == ENTITY_OVERRIDDEN) { + $label = t('Overridden'); + $help = t('This configuration is provided by a module, but has been changed.'); + return $html ? "" . $label . "" : $label; + } + elseif ($status & ENTITY_IN_CODE) { + $label = t('Default'); + $help = t('A module provides this configuration.'); + return $html ? "" . $label . "" : $label; + } + elseif ($status & ENTITY_CUSTOM) { + $label = t('Custom'); + $help = t('A custom configuration by a user.'); + return $html ? "" . $label . "" : $label; + } +} + +/** + * Process variables for entity.tpl.php. + */ +function template_preprocess_entity(&$variables) { + $variables['view_mode'] = $variables['elements']['#view_mode']; + $entity_type = $variables['elements']['#entity_type']; + $variables['entity_type'] = $entity_type; + $entity = $variables['elements']['#entity']; + $variables[$variables['elements']['#entity_type']] = $entity; + $info = entity_get_info($entity_type); + + $variables['title'] = check_plain(entity_label($entity_type, $entity)); + + $uri = entity_uri($entity_type, $entity); + $variables['url'] = $uri && !empty($uri['path']) ? url($uri['path'], $uri['options']) : FALSE; + + if (isset($variables['elements']['#page'])) { + // If set by the caller, respect the page property. + $variables['page'] = $variables['elements']['#page']; + } + else { + // Else, try to automatically detect it. + $variables['page'] = $uri && !empty($uri['path']) && $uri['path'] == $_GET['q']; + } + + // Helpful $content variable for templates. + $variables['content'] = array(); + foreach (element_children($variables['elements']) as $key) { + $variables['content'][$key] = $variables['elements'][$key]; + } + + if (!empty($info['fieldable'])) { + // Make the field variables available with the appropriate language. + field_attach_preprocess($entity_type, $entity, $variables['content'], $variables); + } + list(, , $bundle) = entity_extract_ids($entity_type, $entity); + + // Gather css classes. + $variables['classes_array'][] = drupal_html_class('entity-' . $entity_type); + $variables['classes_array'][] = drupal_html_class($entity_type . '-' . $bundle); + + // Add RDF type and about URI. + if (module_exists('rdf')) { + $variables['attributes_array']['about'] = empty($uri['path']) ? NULL: url($uri['path']); + $variables['attributes_array']['typeof'] = empty($entity->rdf_mapping['rdftype']) ? NULL : $entity->rdf_mapping['rdftype']; + } + + // Add suggestions. + $variables['theme_hook_suggestions'][] = $entity_type; + $variables['theme_hook_suggestions'][] = $entity_type . '__' . $bundle; + $variables['theme_hook_suggestions'][] = $entity_type . '__' . $bundle . '__' . $variables['view_mode']; + if ($id = entity_id($entity_type, $entity)) { + $variables['theme_hook_suggestions'][] = $entity_type . '__' . $id; + } +} diff --git a/sites/all/modules/contrib/dev/entity/entity.tpl.php b/sites/all/modules/contrib/dev/entity/theme/entity.tpl.php similarity index 100% rename from sites/all/modules/contrib/dev/entity/entity.tpl.php rename to sites/all/modules/contrib/dev/entity/theme/entity.tpl.php diff --git a/sites/all/modules/contrib/dev/entity/views/entity.views.inc b/sites/all/modules/contrib/dev/entity/views/entity.views.inc index 23c6f6b..a0179c0 100644 --- a/sites/all/modules/contrib/dev/entity/views/entity.views.inc +++ b/sites/all/modules/contrib/dev/entity/views/entity.views.inc @@ -379,6 +379,7 @@ class EntityDefaultViewsController { $label = isset($this->info['plural label']) ? $this->info['plural label'] : $this->info['label']; $data[$table]['table']['base'] = array( 'field' => $this->info['entity keys']['id'], + 'access query tag' => $this->type . '_access', 'title' => drupal_ucfirst($label), 'help' => isset($this->info['description']) ? $this->info['description'] : '', ); @@ -388,6 +389,50 @@ class EntityDefaultViewsController { // Add in any reverse-relationships which have been determined. $data += $this->relationships; } + if (!empty($this->info['revision table']) && !empty($this->info['entity keys']['revision'])) { + $revision_table = $this->info['revision table']; + + $data[$table]['table']['default_relationship'] = array( + $revision_table => array( + 'table' => $revision_table, + 'field' => $this->info['entity keys']['revision'], + ), + ); + + // Define the base group of this table. Fields that don't + // have a group defined will go into this field by default. + $data[$revision_table]['table']['group'] = drupal_ucfirst($this->info['label']) . ' ' . t('Revisions'); + $data[$revision_table]['table']['entity type'] = $this->type; + + // If the plural label isn't available, use the regular label. + $label = isset($this->info['plural label']) ? $this->info['plural label'] : $this->info['label']; + $data[$revision_table]['table']['base'] = array( + 'field' => $this->info['entity keys']['revision'], + 'access query tag' => $this->type . '_access', + 'title' => drupal_ucfirst($label) . ' ' . t('Revisions'), + 'help' => (isset($this->info['description']) ? $this->info['description'] . ' ' : '') . t('Revisions'), + ); + $data[$revision_table]['table']['entity type'] = $this->type; + $data[$revision_table] += $this->schema_revision_fields(); + + // Add in any reverse-relationships which have been determined. + $data += $this->relationships; + + // For other base tables, explain how we join. + $data[$revision_table]['table']['join'] = array( + // Directly links to base table. + $table => array( + 'left_field' => $this->info['entity keys']['revision'], + 'field' => $this->info['entity keys']['revision'], + ), + ); + $data[$revision_table]['table']['default_relationship'] = array( + $table => array( + 'table' => $table, + 'field' => $this->info['entity keys']['id'], + ), + ); + } return $data; } @@ -410,6 +455,27 @@ class EntityDefaultViewsController { return $data; } + /** + * Try to come up with some views fields with the help of the revision schema + * and the entity property information. + */ + protected function schema_revision_fields() { + $data = array(); + if (!empty($this->info['revision table'])) { + $schema = drupal_get_schema($this->info['revision table']); + $properties = entity_get_property_info($this->type) + array('properties' => array()); + + foreach ($properties['properties'] as $name => $property_info) { + if (isset($property_info['schema field']) && isset($schema['fields'][$property_info['schema field']])) { + if ($views_info = $this->map_from_schema_info($name, $schema['fields'][$property_info['schema field']], $property_info)) { + $data[$name] = $views_info; + } + } + } + } + return $data; + } + /** * Comes up with views information based on the given schema and property * info. @@ -539,6 +605,28 @@ class EntityDefaultViewsController { ); break; + case 'duration': + $return += $description + array( + 'field' => array( + 'real field' => $views_field_name, + 'handler' => 'entity_views_handler_field_duration', + 'click sortable' => TRUE, + ), + 'sort' => array( + 'real field' => $views_field_name, + 'handler' => 'views_handler_sort', + ), + 'filter' => array( + 'real field' => $views_field_name, + 'handler' => 'views_handler_filter_numeric', + ), + 'argument' => array( + 'real field' => $views_field_name, + 'handler' => 'views_handler_argument_numeric', + ), + ); + break; + case 'uri': $return += $description + array( 'field' => array( diff --git a/sites/all/modules/contrib/dev/entity/views/handlers/entity_views_field_handler_helper.inc b/sites/all/modules/contrib/dev/entity/views/handlers/entity_views_field_handler_helper.inc index 55201ab..6bb4fbf 100644 --- a/sites/all/modules/contrib/dev/entity/views/handlers/entity_views_field_handler_helper.inc +++ b/sites/all/modules/contrib/dev/entity/views/handlers/entity_views_field_handler_helper.inc @@ -18,7 +18,7 @@ class EntityFieldHandlerHelper { * Provide appropriate default options for a handler. */ public static function option_definition($handler) { - if (entity_property_list_extract_type($handler->definition['type'])) { + if (isset($handler->definition['type']) && entity_property_list_extract_type($handler->definition['type'])) { $options['list']['contains']['mode'] = array('default' => 'collapse'); $options['list']['contains']['separator'] = array('default' => ', '); $options['list']['contains']['type'] = array('default' => 'ul'); @@ -32,7 +32,7 @@ class EntityFieldHandlerHelper { * Provide an appropriate default option form for a handler. */ public static function options_form($handler, &$form, &$form_state) { - if (entity_property_list_extract_type($handler->definition['type'])) { + if (isset($handler->definition['type']) && entity_property_list_extract_type($handler->definition['type'])) { $form['list']['mode'] = array( '#type' => 'select', '#title' => t('List handling'), @@ -196,8 +196,14 @@ class EntityFieldHandlerHelper { if ($handler->relationship) { $current_handler = $handler; $view = $current_handler->view; - while (!empty($current_handler->relationship) && !empty($view->relationship[$current_handler->relationship])) { - $current_handler = $view->relationship[$current_handler->relationship]; + $relationships = array(); + // Collect all relationships, keyed by alias. + foreach ($view->relationship as $key => $relationship) { + $key = $relationship->alias ? $relationship->alias : $key; + $relationships[$key] = $relationship; + } + while (!empty($current_handler->relationship) && !empty($relationships[$current_handler->relationship])) { + $current_handler = $relationships[$current_handler->relationship]; $return = $current_handler->real_field . ($return ? ":$return" : ''); } } @@ -337,7 +343,7 @@ class EntityFieldHandlerHelper { $values->_entity_properties[$selector] = $wrapper->value(); } else { - $values->_entity_properties[$selector] = isset($wrapper->$field) ? $wrapper->$field->value(array('identifier' => TRUE)) : $default; + $values->_entity_properties[$selector] = isset($wrapper->$field) ? $wrapper->$field->value(array('identifier' => TRUE, 'sanitize' => TRUE)) : $default; } } catch (EntityMetadataWrapperException $e) { diff --git a/sites/all/modules/contrib/dev/entity/views/handlers/entity_views_handler_area_entity.inc b/sites/all/modules/contrib/dev/entity/views/handlers/entity_views_handler_area_entity.inc index de5863b..0c5b714 100644 --- a/sites/all/modules/contrib/dev/entity/views/handlers/entity_views_handler_area_entity.inc +++ b/sites/all/modules/contrib/dev/entity/views/handlers/entity_views_handler_area_entity.inc @@ -10,6 +10,7 @@ class entity_views_handler_area_entity extends views_handler_area { $options['entity_type'] = array('default' => 'node'); $options['entity_id'] = array('default' => ''); $options['view_mode'] = array('default' => 'full'); + $options['bypass_access'] = array('default' => FALSE); return $options; } @@ -39,7 +40,7 @@ class entity_views_handler_area_entity extends views_handler_area { $form['entity_id'] = array( '#type' => 'textfield', '#title' => t('Entity id'), - '#description' => t('Choose the entity you want to display in the area.'), + '#description' => t('Choose the entity you want to display in the area. To render an entity given by a contextual filter use "%1" for the first argument, "%2" for the second, etc.'), '#default_value' => $this->options['entity_id'], ); @@ -73,6 +74,12 @@ class entity_views_handler_area_entity extends views_handler_area { ); } } + $form['bypass_access'] = array( + '#type' => 'checkbox', + '#title' => t('Bypass access checks'), + '#description' => t('If enabled, access permissions for rendering the entity are not checked.'), + '#default_value' => !empty($this->options['bypass_access']), + ); return $form; } @@ -98,14 +105,46 @@ class entity_views_handler_area_entity extends views_handler_area { * Render an entity using the view mode. */ public function render_entity($entity_type, $entity_id, $view_mode) { + $tokens = $this->get_render_tokens(); + // Replace argument tokens in entity id. + $entity_id = strtr($entity_id, $tokens); if (!empty($entity_type) && !empty($entity_id) && !empty($view_mode)) { - $entities = entity_load($entity_type, array($entity_id)); - $render = entity_view($entity_type, $entities, $view_mode); - $render_entity = reset($render); - return drupal_render($render_entity); + $entity = entity_load_single($entity_type, $entity_id); + if (!empty($this->options['bypass_access']) || entity_access('view', $entity_type, $entity)) { + $render = entity_view($entity_type, array($entity), $view_mode); + $render_entity = reset($render); + return drupal_render($render_entity); + } } else { return ''; } } + + /** + * Get the 'render' tokens to use for advanced rendering. + * + * This runs through all of the fields and arguments that + * are available and gets their values. This will then be + * used in one giant str_replace(). + */ + function get_render_tokens() { + $tokens = array(); + if (!empty($this->view->build_info['substitutions'])) { + $tokens = $this->view->build_info['substitutions']; + } + $count = 0; + foreach ($this->view->display_handler->get_handlers('argument') as $arg => $handler) { + $token = '%' . ++$count; + if (!isset($tokens[$token])) { + $tokens[$token] = ''; + } + // Use strip tags as there should never be HTML in the path. + // However, we need to preserve special characters like " that + // were removed by check_plain(). + $tokens['%' . $count] = $handler->argument; + } + + return $tokens; + } } diff --git a/sites/all/modules/contrib/dev/entity/views/handlers/entity_views_handler_field_duration.inc b/sites/all/modules/contrib/dev/entity/views/handlers/entity_views_handler_field_duration.inc index 53454b2..2850e6a 100644 --- a/sites/all/modules/contrib/dev/entity/views/handlers/entity_views_handler_field_duration.inc +++ b/sites/all/modules/contrib/dev/entity/views/handlers/entity_views_handler_field_duration.inc @@ -122,8 +122,10 @@ class entity_views_handler_field_duration extends views_handler_field { if ($this->options['format_interval']) { $value = format_interval($value, (int) $this->options['granularity']); } + // Value sanitization is handled by the wrapper, see + // EntityFieldHandlerHelper::get_value(). return $this->sanitize_value($this->options['prefix'], 'xss') . - $this->sanitize_value($value) . + $value . $this->sanitize_value($this->options['suffix'], 'xss'); } diff --git a/sites/all/modules/contrib/dev/entity/views/handlers/entity_views_handler_field_entity.inc b/sites/all/modules/contrib/dev/entity/views/handlers/entity_views_handler_field_entity.inc index f76fb17..d9fe73b 100644 --- a/sites/all/modules/contrib/dev/entity/views/handlers/entity_views_handler_field_entity.inc +++ b/sites/all/modules/contrib/dev/entity/views/handlers/entity_views_handler_field_entity.inc @@ -80,6 +80,7 @@ class entity_views_handler_field_entity extends views_handler_field { $options['display'] = array('default' => 'label'); $options['link_to_entity']['default'] = TRUE; $options['view_mode'] = array('default' => 'default'); + $options['bypass_access'] = array('default' => FALSE); return $options; } @@ -134,6 +135,12 @@ class entity_views_handler_field_entity extends views_handler_field { '#value' => $options ? key($options) : 'default', ); } + $form['bypass_access'] = array( + '#type' => 'checkbox', + '#title' => t('Bypass access checks'), + '#description' => t('If enabled, access permissions for rendering the entity are not checked.'), + '#default_value' => !empty($this->options['bypass_access']), + ); } public function render($values) { @@ -175,7 +182,8 @@ class entity_views_handler_field_entity extends views_handler_field { if (!is_object($entity) && isset($entity) && $entity !== FALSE) { $entity = entity_load_single($type, $entity); } - if (!$entity) { + // Make sure the entity exists and access is either given or bypassed. + if (!$entity || !(!empty($this->options['bypass_access']) || entity_access('view', $type, $entity))) { return ''; } diff --git a/sites/all/modules/contrib/dev/entity/views/handlers/entity_views_handler_field_options.inc b/sites/all/modules/contrib/dev/entity/views/handlers/entity_views_handler_field_options.inc index a947d36..8792c83 100644 --- a/sites/all/modules/contrib/dev/entity/views/handlers/entity_views_handler_field_options.inc +++ b/sites/all/modules/contrib/dev/entity/views/handlers/entity_views_handler_field_options.inc @@ -112,8 +112,9 @@ class entity_views_handler_field_options extends views_handler_field { if ($this->options['format_name'] && isset($this->option_list[$value])) { $value = $this->option_list[$value]; } - - return $this->sanitize_value($value); + // Sanitization is handled by the wrapper, see + // EntityFieldHandlerHelper::get_value(). + return $value; } } diff --git a/sites/all/modules/contrib/dev/entity/views/handlers/entity_views_handler_field_text.inc b/sites/all/modules/contrib/dev/entity/views/handlers/entity_views_handler_field_text.inc index a7921da..425abf2 100644 --- a/sites/all/modules/contrib/dev/entity/views/handlers/entity_views_handler_field_text.inc +++ b/sites/all/modules/contrib/dev/entity/views/handlers/entity_views_handler_field_text.inc @@ -93,7 +93,9 @@ class entity_views_handler_field_text extends views_handler_field { * Render a single field value. */ public function render_single_value($value, $values) { - return $this->sanitize_value($value, 'xss'); + // Sanitization is handled by the wrapper, see + // EntityFieldHandlerHelper::get_value(). + return $value; } } diff --git a/sites/all/modules/contrib/dev/entity/views/handlers/entity_views_handler_relationship_by_bundle.inc b/sites/all/modules/contrib/dev/entity/views/handlers/entity_views_handler_relationship_by_bundle.inc index 55b0fc2..a4d09d3 100644 --- a/sites/all/modules/contrib/dev/entity/views/handlers/entity_views_handler_relationship_by_bundle.inc +++ b/sites/all/modules/contrib/dev/entity/views/handlers/entity_views_handler_relationship_by_bundle.inc @@ -93,7 +93,7 @@ class entity_views_handler_relationship_by_bundle extends views_handler_relation $def['extra'] = array( array( // The table and the IN operator are implicit. - 'field' => $entity_info['bundle keys']['bundle'], + 'field' => $entity_info['entity keys']['bundle'], 'value' => $this->options['bundle_types'], ), ); diff --git a/sites/all/modules/contrib/dev/entity/views/plugins/entity_views_plugin_row_entity_view.inc b/sites/all/modules/contrib/dev/entity/views/plugins/entity_views_plugin_row_entity_view.inc index db72b5f..5e738a8 100644 --- a/sites/all/modules/contrib/dev/entity/views/plugins/entity_views_plugin_row_entity_view.inc +++ b/sites/all/modules/contrib/dev/entity/views/plugins/entity_views_plugin_row_entity_view.inc @@ -88,6 +88,9 @@ class entity_views_plugin_row_entity_view extends views_plugin_row { public function render($values) { if ($entity = $this->get_value($values)) { + // Add the view object as views_plugin_row_node_view::render() would. + // Otherwise the views theme suggestions won't work properly. + $entity->view = $this->view; $render = $this->rendered_content[entity_id($this->entity_type, $entity)]; return drupal_render($render); } diff --git a/sites/all/modules/contrib/fields/autocomplete_deluxe/autocomplete_deluxe-rtl.css b/sites/all/modules/contrib/fields/autocomplete_deluxe/autocomplete_deluxe-rtl.css new file mode 100644 index 0000000..056051c --- /dev/null +++ b/sites/all/modules/contrib/fields/autocomplete_deluxe/autocomplete_deluxe-rtl.css @@ -0,0 +1,36 @@ +span.autocomplete-deluxe-value-delete { + float: left; +} +div.autocomplete-deluxe-multiple { + padding: 4px 5px; +} + +input.autocomplete-deluxe-form.autocomplete-deluxe-multiple { + margin-left: 0; + margin-right: 5px; + float: right; +} + +div.autocomplete-deluxe-throbber { + float: left; +} + +.autocomplete-deluxe-closed { + background-position: 0 6px; +} + +.autocomplete-deluxe-open { + background-position: 0 -14px; +} + +.autocomplete-deluxe-item { + float: right; + margin: 3px 5px 3px 0; + padding: 3px 5px 3px 20px; +} + +.autocomplete-deluxe-item-delete { + position: absolute; + right: auto; + left: 3px; +} diff --git a/sites/all/modules/contrib/fields/autocomplete_deluxe/autocomplete_deluxe.api.php b/sites/all/modules/contrib/fields/autocomplete_deluxe/autocomplete_deluxe.api.php index 29d26f5..80026c6 100755 --- a/sites/all/modules/contrib/fields/autocomplete_deluxe/autocomplete_deluxe.api.php +++ b/sites/all/modules/contrib/fields/autocomplete_deluxe/autocomplete_deluxe.api.php @@ -1,5 +1,4 @@ 'a', 2 => - * 'b', 3 => 'c'). + * - #autocomplete_options needs an array in the form of an array (similar to + * #options in core for selects or checkboxes): array('a', 'b', 'c') or + * array(1 => 'a', 2 => 'b', 3 => 'c'). * - * Besides this two, there are three other options, wich autocomplete deluxe + * Besides these two, there are four other options which autocomplete deluxe * accepts: * - #multiple Indicates whether the user may select more than one item. Expects * TRUE or FALSE, by default it is set to FALSE. - * - #autocomplete_multiple_delimiter If #multiple is TRUE, then you can use - * this option to set a seperator for multiple values. By default a string - * with the follwing content will be used: ', '. - * - #autocomplete_min_length Indicates how many characters must be entered - * until, the suggesion list can be opened. Especially helpfull, when your + * - #delimiter If #multiple is TRUE, then you can use this option to set a + * seperator for multiple values. By default a string with the following + * content will be used: ', '. + * - #min_length Indicates how many characters must be entered + * until, the suggesion list can be opened. Especially helpful, when your * ajax callback returns only valid suggestion for a minimum characters. * The default is 0. + * - #not_found_message A message text which will be displayed, if the entered + * term was not found. */ function somefunction() { switch ($type) { @@ -38,16 +39,18 @@ function somefunction() { '#type' => 'autocomplete_deluxe', '#autocomplete_options' => $options, '#multiple' => FALSE, - '#autocomplete_min_length' => 0, + '#min_length' => 0, ); break; + case 'ajax': $element = array( '#type' => 'autocomplete_deluxe', '#autocomplete_deluxe_path' => url('some_uri', array('absolute' => TRUE)), '#multiple' => TRUE, - '#autocomplete_min_length' => 1, - '#autocomplete_multiple_delimiter' => '|', + '#min_length' => 1, + '#delimiter' => '|', + '#not_found_message' => "The term '@term' will be added.", ); break; } diff --git a/sites/all/modules/contrib/fields/autocomplete_deluxe/autocomplete_deluxe.css b/sites/all/modules/contrib/fields/autocomplete_deluxe/autocomplete_deluxe.css index 3db113d..795f369 100755 --- a/sites/all/modules/contrib/fields/autocomplete_deluxe/autocomplete_deluxe.css +++ b/sites/all/modules/contrib/fields/autocomplete_deluxe/autocomplete_deluxe.css @@ -12,7 +12,7 @@ a.autocomplete-deluxe-single:hover { text-decoration: none; } -.ui-state-hover { +.ui-autocomplete .ui-state-hover { background-color: #3875d7; background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(20%, #3875d7), color-stop(90%, #2a62bc)); background-image: -webkit-linear-gradient(top, #3875d7 20%, #2a62bc 90%); @@ -114,7 +114,7 @@ div.autocomplete-deluxe-multiple { background: no-repeat -38px -22px, -o-linear-gradient(top, #eeeeee 1%, #ffffff 15%); background: no-repeat -38px -22px, -ms-linear-gradient(top, #eeeeee 1%, #ffffff 15%); background: no-repeat -38px -22px, linear-gradient(top, #eeeeee 1%, #ffffff 15%); - padding: 4px 5px 4px 20px; + padding: 4px 5px; border: 1px solid #AAAAAA; cursor: text; height: auto !important; diff --git a/sites/all/modules/contrib/fields/autocomplete_deluxe/autocomplete_deluxe.info b/sites/all/modules/contrib/fields/autocomplete_deluxe/autocomplete_deluxe.info index fe27855..9e31eb7 100644 --- a/sites/all/modules/contrib/fields/autocomplete_deluxe/autocomplete_deluxe.info +++ b/sites/all/modules/contrib/fields/autocomplete_deluxe/autocomplete_deluxe.info @@ -5,9 +5,9 @@ core = 7.x files[] = autocomplete_deluxe.module dependencies[] = taxonomy -; Information added by drupal.org packaging script on 2013-01-22 -version = "7.x-2.0-beta2+22-dev" +; Information added by Drupal.org packaging script on 2017-07-25 +version = "7.x-2.3" core = "7.x" project = "autocomplete_deluxe" -datestamp = "1358814920" +datestamp = "1501005546" diff --git a/sites/all/modules/contrib/fields/autocomplete_deluxe/autocomplete_deluxe.js b/sites/all/modules/contrib/fields/autocomplete_deluxe/autocomplete_deluxe.js index 4f942eb..b5e641b 100755 --- a/sites/all/modules/contrib/fields/autocomplete_deluxe/autocomplete_deluxe.js +++ b/sites/all/modules/contrib/fields/autocomplete_deluxe/autocomplete_deluxe.js @@ -82,7 +82,26 @@ return this; }; + /** + * Unescapes the given string. + */ + Drupal.autocomplete_deluxe.unescape = function (input) { + // Unescaping is done via a textarea, since the text inside of it is never + // executed. This method also allows us to support older browsers like + // IE 9 and below. + var textArea = document.createElement('textarea'); + textArea.innerHTML = input; + var decoded = textArea.value; + if ('remove' in Element.prototype) { + textArea.remove(); + } + return decoded; + }; + /** + * If there is no result this label will be shown. + * @type {{label: string, value: string}} + */ Drupal.autocomplete_deluxe.empty = {label: '- ' + Drupal.t('None') + ' -', value: "" }; /** @@ -105,6 +124,9 @@ Drupal.autocomplete_deluxe.Widget = function() { }; + /** + * Url for the callback. + */ Drupal.autocomplete_deluxe.Widget.prototype.uri = null; /** @@ -119,7 +141,7 @@ }; Drupal.autocomplete_deluxe.Widget.prototype.init = function(settings) { - if ($.browser.msie && $.browser.version === "6.0") { + if(navigator.appVersion.indexOf("MSIE 6.") != -1) { return; } @@ -130,7 +152,17 @@ this.multiple = settings.multiple; this.required = settings.required; this.limit = settings.limit; - this.synonyms = settings.use_synonyms === undefined ? false : settings.use_synonyms; + this.synonyms = typeof settings.use_synonyms == 'undefined' ? false : settings.use_synonyms; + this.not_found_message = typeof settings.use_synonyms == 'undefined' ? "The term '@term' will be added." : settings.not_found_message; + + this.wrapper = '""'; + + if (typeof settings.delimiter == 'undefined') { + this.delimiter = true; + } else { + this.delimiter = settings.delimiter.charCodeAt(0); + } + this.items = {}; var self = this; @@ -153,7 +185,7 @@ } if ($.isEmptyObject(result)) { result.push({ - label: Drupal.t("The term '@term' will be added.", {'@term' : term}), + label: Drupal.t(self.not_found_message, {'@term' : term}), value: term, newTerm: true }); @@ -161,7 +193,7 @@ return result; }; - var cache = {} + var cache = {}; var lastXhr = null; this.source = function(request, response) { @@ -193,6 +225,9 @@ }); var jqObject = this.jqObject; + + var autocompleteDataKey = typeof(this.jqObject.data('autocomplete')) === 'object' ? 'item.autocomplete' : 'ui-autocomplete'; + var throbber = $('
     
    ').insertAfter(jqObject); this.jqObject.bind("autocompletesearch", function(event, ui) { @@ -214,8 +249,9 @@ var re = new RegExp('()*""' + escapedValue + '""|' + escapedValue + '()*', 'gi'); var t = item.label.replace(re,"$&"); } + return $( "
  • " ) - .data( "item.autocomplete", item ) + .data(autocompleteDataKey, item) .append( "" + t + "" ) .appendTo( ul ); }; @@ -276,14 +312,17 @@ } this.value = item.value; - this.element = $('' + item.label + ''); + this.element = $(''); + this.element.text(item.label); this.widget = widget; this.item = item; var self = this; var close = $('').appendTo(this.element); // Use single quotes because of the double quote encoded stuff. - var input = $('').appendTo(this.element); + var input = $('') + input.val(this.value); + input.appendTo(this.element); close.mousedown(function() { self.remove(item); @@ -294,25 +333,28 @@ this.element.remove(); var values = this.widget.valueForm.val(); var escapedValue = Drupal.autocomplete_deluxe.escapeRegex( this.item.value ); - var regex = new RegExp('()*""' + escapedValue + '""|' + escapedValue + '()*', 'gi'); + var regex = new RegExp('()*""' + escapedValue + '""()*', 'gi'); this.widget.valueForm.val(values.replace(regex, '')); delete this.widget.items[this.value]; }; Drupal.autocomplete_deluxe.MultipleWidget.prototype.setup = function() { var jqObject = this.jqObject; - var parent = jqObject.parent(); - var value_container = jqObject.parent().parent().children('.autocomplete-deluxe-value-container'); - var value_input = value_container.children().children(); + var parent = jqObject.parents('.autocomplete-deluxe-container'); + var value_container = parent.next(); + var value_input = value_container.find('input'); var items = this.items; var self = this; this.valueForm = value_input; // Override the resize function, so that the suggestion list doesn't resizes // all the time. - jqObject.data("autocomplete")._resizeMenu = function() {}; + var autocompleteDataKey = typeof(this.jqObject.data('autocomplete')) === 'object' ? 'autocomplete' : 'ui-autocomplete'; + + jqObject.data(autocompleteDataKey)._resizeMenu = function() {}; jqObject.show(); + value_container.hide(); // Add the default values to the box. @@ -346,7 +388,7 @@ var item = new Drupal.autocomplete_deluxe.MultipleWidget.Item(self, ui_item); item.element.insertBefore(jqObject); items[ui_item.value] = item; - var new_value = ' ""' + ui_item.value + '""'; + var new_value = ' ' + self.wrapper + ui_item.value + self.wrapper; var values = value_input.val(); value_input.val(values + new_value); jqObject.val(''); @@ -358,7 +400,13 @@ }); jqObject.bind("autocompleteselect", function(event, ui) { - self.addValue(ui.item); + // JQuery ui autocomplete needs the terms escaped, otherwise it would be + // open to XSS issues. Drupal.autocomplete.Item also escapes on rendering + // the DOM elements. Thus we have to unescape the label here before adding + // the new item. + var item = ui.item; + item.label = Drupal.autocomplete_deluxe.unescape(item.label); + self.addValue(item); jqObject.width(25); // Return false to prevent setting the last term as value for the jqObject. return false; @@ -375,13 +423,13 @@ var clear = false; - jqObject.keydown(function (event) { + jqObject.keypress(function (event) { var value = jqObject.val(); // If a comma was entered and there is none or more then one comma,or the // enter key was entered, then enter the new term. - if ((event.which == 188 && (value.split('"').length - 1) != 1) || (event.which == 13 && jqObject.val() != "")) { + if ((event.which == self.delimiter && (value.split('"').length - 1) != 1) || (event.which == 13 && jqObject.val() != "")) { value = value.substr(0, value.length); - if (self.items[value] === undefined && value != '') { + if (typeof self.items[value] == 'undefined' && value != '') { var ui_item = { label: value, value: value diff --git a/sites/all/modules/contrib/fields/autocomplete_deluxe/autocomplete_deluxe.module b/sites/all/modules/contrib/fields/autocomplete_deluxe/autocomplete_deluxe.module index 85c1cfd..a1efa26 100755 --- a/sites/all/modules/contrib/fields/autocomplete_deluxe/autocomplete_deluxe.module +++ b/sites/all/modules/contrib/fields/autocomplete_deluxe/autocomplete_deluxe.module @@ -27,7 +27,7 @@ function autocomplete_deluxe_field_widget_info() { /** * Custom taxonomy callback, which also accepts an empty string search. */ -function taxonomy_autocomplete_deluxe($field_name, $tags_typed = '', $limit = 10) { +function autocomplete_deluxe_taxonomy_callback($field_name, $tags_typed = '', $limit = 10) { $field = field_info_field($field_name); $use_synonyms = !empty($_GET['synonyms']); @@ -156,6 +156,19 @@ function autocomplete_deluxe_field_widget_settings_form($field, $instance) { '#default_value' => isset($settings['min_length']) ? $settings['min_length'] : 0, '#element_validate' => array('_element_validate_integer'), ); + $form['delimiter'] = array( + '#type' => 'textfield', + '#title' => t('Delimiter.'), + '#description' => t('A character which should be used beside the enter key, to seperate terms.'), + '#default_value' => isset($settings['delimiter']) ? $settings['delimiter'] : '', + '#size' => 1, + ); + $form['not_found_message'] = array( + '#type' => 'textfield', + '#title' => t('Term not found message.'), + '#description' => t('A message text which will be displayed, if the entered term was not found.'), + '#default_value' => isset($settings['not_found_message']) ? $settings['not_found_message'] : "The term '@term' will be added.", + ); if (module_exists('synonyms')) { $form['use_synonyms'] = array( @@ -211,6 +224,8 @@ function autocomplete_deluxe_field_widget_form(&$form, &$form_state, $field, $in '#limit' => isset($instance['widget']['settings']['limit']) ? $instance['widget']['settings']['limit'] : 10, '#min_length' => isset($instance['widget']['settings']['min_length']) ? $instance['widget']['settings']['min_length'] : 0, '#use_synonyms' =>isset($instance['widget']['settings']['use_synonyms']) ? $instance['widget']['settings']['use_synonyms'] : 0, + '#delimiter' =>isset($instance['widget']['settings']['delimiter']) ? $instance['widget']['settings']['delimiter'] : '', + '#not_found_message' =>isset($instance['widget']['settings']['not_found_message']) ? $instance['widget']['settings']['not_found_message'] : "The term '@term' will be added.", ); $multiple = $field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED ? TRUE : FALSE; @@ -262,12 +277,13 @@ function autocomplete_deluxe_element_process($element) { $js_settings['autocomplete_deluxe'][$html_id] = array( 'input_id' => $html_id, - 'min_length' => isset($element['#autocomplete_min_length']) ? $element['#autocomplete_min_length'] : 0, 'multiple' => $element['#multiple'], 'required' => $element['#required'], 'limit' => isset($element['#limit']) ? $element['#limit'] : 10, 'min_length' => isset($element['#min_length']) ? $element['#min_length'] : 0, 'use_synonyms' => isset($element['#use_synonyms']) ? $element['#use_synonyms'] : 0, + 'delimiter' => isset($element['#delimiter']) ? $element['#delimiter'] : '', + 'not_found_message' => isset($element['#not_found_message']) ? $element['#not_found_message'] : "The term '@term' will be added.", ); if (isset($element['#autocomplete_deluxe_path'])) { @@ -307,6 +323,7 @@ function autocomplete_deluxe_element_process($element) { return $element; } $element['#attached']['js'][] = array('data' => $js_settings, 'type' => 'setting'); + $element['#tree'] = TRUE; return $element; } @@ -343,9 +360,11 @@ function autocomplete_deluxe_after_build($element, &$form_state) { // Replace all double double quotes and space with a comma. This will allows // us to keep entries in double quotes. $element['#value'] = str_replace('"" ""', ',', $element['#value']); + $element['#value'] = str_replace('"" ""', ',', $element['#value']); // Remove the double quotes at the beginning and the end from the first and // the last term. $element['#value'] = substr($element['#value'], 2, strlen($element['#value']) - 4); + unset($element['value_field']['#maxlength']); } @@ -373,7 +392,7 @@ function autocomplete_deluxe_element_info() { function autocomplete_deluxe_menu() { $items['autocomplete_deluxe/taxonomy'] = array( 'title' => 'Autocomplete deluxe taxonomy', - 'page callback' => 'taxonomy_autocomplete_deluxe', + 'page callback' => 'autocomplete_deluxe_taxonomy_callback', 'access arguments' => array('access content'), 'type' => MENU_CALLBACK, ); diff --git a/sites/all/modules/contrib/fields/date/.gitignore b/sites/all/modules/contrib/fields/date/.gitignore new file mode 100644 index 0000000..a023882 --- /dev/null +++ b/sites/all/modules/contrib/fields/date/.gitignore @@ -0,0 +1,4 @@ +*.patch +*.diff +.idea/ +.idea/* diff --git a/sites/all/modules/contrib/fields/date/CHANGELOG.txt b/sites/all/modules/contrib/fields/date/CHANGELOG.txt index 99e8fb0..e5e655e 100644 --- a/sites/all/modules/contrib/fields/date/CHANGELOG.txt +++ b/sites/all/modules/contrib/fields/date/CHANGELOG.txt @@ -1,9 +1,67 @@ Date Module 7.x ================= -=================== -Version 7.x-2.x-dev -=================== +====================== +Version 7.x-2.8 +====================== +- Issue #106713 by vijaycs85, heddn: Date Field title XSS(SA) +- Issue #2310123 by Temoor: Fixed Fatal error in Migrate UI by Date Migrate Example. +- Issue #2142277 by Temoor | lliss: Fixed Infinite Loop When Using Repeating Dates. +- Issue #1871136 by Temoor | funkimunky: Fixed Cannot remove migrate example content type. +- Issue #2261395 by minorOffense | kristofferwiklund: Fixed date_now is not respecting changes to timezone. +- Issue #1832400 by imot3k, balintk: Fixed "The year/month/... is missing" error message is not translatable. +- Issue #998076 by coredumperror, joelcollinsdc, brenk28: Fixed Problem with timezone handling (caused by date_get_timezone_db returning only UTC). +- Issue #1976014 by Harmageddon: Remove unnecessary   in date views pager. +- Issue #2198807 by stickywes: Refactor field tests to utilize DateFieldBasic. +- Issue #2146461 by vijaycs85 | Razia_b: Fixed Translated field's label is not used in its validation messages. +- Issue #1874422 by bluetegu, brockfanning, rv0: Fixed Week pager may cause incorrect "next" link on year change. +- Issue #2167015 by schifazl: HTML output breaks validation and accessibility. +- Issue #2200513 by scor: Fix encoding issues in CHANGELOG.txt. +- Issue #2115377 by coredumperror: Notice: Undefined index: add_microdata in theme_date_display_single(). +- Issue #2178299 by sandykadam | joachim: Incorrect variable name in hook_views_data_alter(). +- Issue #1431952 by Cadila | Shawn DeArmond: Date field will only set in Rules if it already has a value. +- Issue #355058 by Grimreaper | prunelle: Hardcoded day/month/week output format for theme_date_nav_title(). +- Issue #1968828 by pjcdawkins: Replace field_info_fields() with field_info_field_map() for Drupal >= 7.22. +- Issue #2167033 by podarok, vijaycs85: Fixing tests in 2014 . + +====================== +Version 7.x-2.7 +====================== +- Issue #1691342 by Cyberwolf: Field description is displayed multiple times. +- Issue #1668240 by ianthomas_uk: /themes/jquery.timeentry.css is not aggregated with other CSS files. +- Issue #1974056 by pjcdawkins: Add a CSS class to the date repeat rule. +- Issue #2065749 by vijaycs85, blackdog: $class in theme_date_repeat_rrule is not used. +- Issue #1840008 by cr0ss, Alan D.: Unlock "Date attributes" (granularity) field setting. +- Issue #1580032 by jmuzz | 30equals: Date values are not being saved when field is used in a nested field collection. +- Issue #2071629 by blackdog: #after_build wrongly added, overwrites other modules. +- Issue #1869962 by kaidjohnson: Date Context breaks context edit form. +- Issue #1571258 by David_Rothstein | ddalvi: Date and time form elements do not have accessible labels when the label position is set to 'Within' or 'None'. +- Issue #2130575 by kostajh: Unset() should be used with more caution in views_filter_handler_simple().inc. +- Issue #1826598 by eromba, jyee: 'c' and 'r' formatter causes date to be printed twice in views. +- Issue #1933472 by minorOffense: Added Optimize date_now() function. +- Issue #1248786 by kenneth.venken | dboulet: Fixed appearance of marker for required date fields. +- Issue #1810734 by ParisLiakos: Fixed Repeat checkboxes working reversed with updated jQuery. +- Issue #1844092 by dboulet, kardave, Spleshka: Fixed Untranslated strings: month, day, ... +- Issue #1202248 by james.williams, dawehner, mikehues | DamienMcKenna: Fixed Exported view doesn't include 'default_argument_options()' attribute. +- Issue #1409120 by anrikun, akamustang | marsbidon: Added Date format in views exposed filter does not respect configured format. +- Issue #1858112 by tomdearden: Fixed DateObject->difference not calculating correctly for future dates > 1 year away. +- Issue #1659466 by artkon: Fixed Date filter does not remember value in views if the identifier is something other than date_filter(). +- Issue #1905096 by gielfeldt: Fixed Wrong usage of database API. +- Issue #1791804 by 5n00py: Errors on form validation if date field placed in sub-form. +- Issue #636208 Date range: Expose a single filter to select events that start before the chosen date and end after the chosen date by anrikun. +- Issue #2024269 Date popup documentation fix by RoySegall. +- Issue #1835184 by Steven Jones, das-peter, jwhat: Fixed date_limit_format() can have poor performance. +- Issue #991830 by milesw | restyler: Fixed validation errors when date_popup() date is localized. +- Issue #2098715 by jhodgdon: Fixed Date field not obeying widget alters for #required. +- Issue #1266688 by linclark, rbayliss, fago, colette: Support microdata in date fields. +- Issue #1863610 by maximpodorov: Fixed Invalid date formatting. +- META #2034231 #1832544 Class registration for Migrate 2.5 or later - mikeryan, [#1835214] Automated tests failing - Exception thrown in Date2 migration - PatchRanger, [# +- Issue #1350604 by Alan D., johaziel: Added Diff support for Date fields. +- Issue #2086313 by dooug: Fixed Wrong path in date_popup() README.txt +- Issue #1455558 by BTMash | mediameriquat: Fixed Missing file in date_views().info causes error 500. +- Issue #1697322 by Alan D., ianmthomasuk | pandikamal: Fixed Call to a member function getName() on a non-object in date api. +- Code cleanup, remove #prev values that were never used in repeat functionality. +- Small fix needed to ensure cardinality gets set correctly if repeat option is changed in the UI. ====================== Version 7.x-2.6 @@ -110,7 +168,7 @@ Version 7.x-2.1 - Issue #1437242 by zerbash, Remove extraneous leading slashes in module_load_include(). - Issue #1436722 by hefox: Fixed Undefined variable $form_set_error() used as function. -- Issue #1250626 by Gbor Hojtsy, B-Prod, hefox: Added start date and end date labels. +- Issue #1250626 by Gábor Hojtsy, B-Prod, hefox: Added start date and end date labels. - Issue #1253482, Make sure $argument->is_default gets reset by the Date pager when altering results. - Adjust Date Tools to work with changes to use Views templates to create calendars. - Issue #1398584 by dhalbert and , Make sure groupby times is initialized. @@ -194,7 +252,7 @@ The Date Browser has been removed. Please use the Date Pager instead. If you hav Browser the navigation will just disappear from them. If you add a Date Pager to the view you should get it back. Then delete the Date Browser attachment from the view, since it doesn't do anything any more. -The UNTIL date was not getting included in repeating results and that is now fixed. This is an API change of sorts +The UNTIL date was not getting included in repeating results and that is now fixed. This is an API change of sorts for anyone who worked around the issue by setting it ahead. The All Day checkbox and All Day themes were moved into a separate module, using new hooks added to the date @@ -219,7 +277,7 @@ New Features/Major Changes Bugfixes - Fix to new default date handling, the default date has to set a date in the database timezone, not the display timezone. -- Issue #1245106 by Gbor Hojtsy, Hide the option to add the delta into the view for single value fields. +- Issue #1245106 by Gábor Hojtsy, Hide the option to add the delta into the view for single value fields. - Issue #1370876, Make sure new Date All Day code does not try to set the popup values if Date Popup is disabled. - Issue #874322, Add back the date_field_all_day() function to avoid breaking other modules that are using it. - Fix Date text placeholder to display a formatted date instead of a format. Follow up to Date repeat UI changes. @@ -316,7 +374,7 @@ Bugfixes - Issue #1179715, Default value callback for the timezone widget was not returning an array. - Issue #1179716, Remove value_callback for date_repeat and date_combo forms, the default behavior works fine. - Issue #1178716 by das-peter, Use drupal_array_get_nested_value() in Date Repeat instead of trying to find it manually. -- Issue #1178716 by das-peter and KarenS, Tweak the date repeat widget to identify empty input when used on nodes with translation. +- Issue #1178716 by das-peter and KarenS, Tweak the date repeat widget to identify empty input when used on nodes with translation. - Issue #1178716 by das-peter and KarenS, Fix date repeat form values that are not arrays when hidden on a node that has translation. - Issue #1178176 by das-peter, Fix date_combo_value_callback to return NULL to avoid data lost on untranslatable dates used with Entity Translation. - Date Context module was making incorrect assumptions about the $language of the field. @@ -350,7 +408,7 @@ field settings. Previous versions did not always honor those settings, this one New Features/UX Improvements -- Issue #1249724 by KarenS, Gbor Hojtsy, David_Rothstein, Improve usability of date and time input configuration. +- Issue #1249724 by KarenS, Gábor Hojtsy, David_Rothstein, Improve usability of date and time input configuration. - Issue #1250784 by David_Rothstein, Add user-friendly labels for start and end date values in Views. - Issue #742146, Add option to remove X-WR-CALNAME if VEVENT is not a feed. - Add option to change method from PUBLISH to REQUEST in VCALENDAR. @@ -360,8 +418,8 @@ New Features/UX Improvements - Issue #1249724 by David_Rothstein: Improve usability of date and time input configuration - Issue #1177198 by tim.plunkett: Allow CTools to process #dependency for date elements. - Issue #1245562 by David_Rothstein, Rename the default date display format to something friendlier -- Issue #1239934 by David_Rothstein and Gbor Hojtsy, Reuse the "years back and forward" dropdown widget on the Views filter settings page. -- Issue #1239228 by Gbor Hojtsy, Date Views filter form UI improvements, clarify the way absolute and relative dates work. +- Issue #1239934 by David_Rothstein and Gábor Hojtsy, Reuse the "years back and forward" dropdown widget on the Views filter settings page. +- Issue #1239228 by Gábor Hojtsy, Date Views filter form UI improvements, clarify the way absolute and relative dates work. - Issue #233047 by ksenzee and David_Rothstein, Add the Vegas jQuery timepicker as a new time selector option. - Issue #1145976 by tim.plunkett and KarenS, Add 'is date' identifier to all date handlers. - Issue #1234140 by arlinsundbulte, Change terminology in user-facing text from 'From/To Date' to 'Start/End Date'. @@ -408,10 +466,10 @@ Bugfixes - Issue #1254582 Repeat additions need to be adjusted to use the same time as the original date. - Move vcalendar and vevent templates from Date Views to Date API modules. - Follow up to Issue #1250344, We don't need extra space when there is a description, only when there is not. -- Issue #1239228 by Gbor Hojtsy, More tweaks to filter css. -- Issue #1244924 by Gbor Hojtsy, Minor text improvements in date filter configuration +- Issue #1239228 by Gábor Hojtsy, More tweaks to filter css. +- Issue #1244924 by Gábor Hojtsy, Minor text improvements in date filter configuration - Issue #1245556 by David_Rothstein, Date granularity description incorrectly implies that it affects the date attributes that are displayed -- Issue #1247444 by Gbor Hojtsy, Give a little breathing space to the date year range "other" field +- Issue #1247444 by Gábor Hojtsy, Give a little breathing space to the date year range "other" field - Issue #1250344 by jessebeach, Fix padding around date fields by adding clearfix class. - Issue #1249116 by yched, Fix various glitches with D6 migration code. - Issue #1243022 by fmosca and KarenS, Make sure all_day #states visibility is only set when there is a value for all_day. @@ -419,7 +477,7 @@ Bugfixes - Issue #1246416, Test whether libraries_get_path() returns a valid path before using it. - Issue #1235994, Don't display 'All Day' when using a format that has no time. - Issue #1245690 by mikeryan, Migration plugin missing seconds from date formats -- Issue #1229406 by David Rothstein, Gbor Hojtsy, and tim.plunkett Fix broken timepicker in Chrome and Safari. +- Issue #1229406 by David Rothstein, Gábor Hojtsy, and tim.plunkett Fix broken timepicker in Chrome and Safari. - Issue #1239412 by keithm, Fix validation error when #access is false. - Issue #1232522, Don't alter field_ui_field_edit form except on date fields. - Issue #1243842, Make sure the All Day and Show End Date flags work correctly in unlimited value fields that use ajax. diff --git a/sites/all/modules/contrib/fields/date/date.api.php b/sites/all/modules/contrib/fields/date/date.api.php index 17f7514..268ae86 100644 --- a/sites/all/modules/contrib/fields/date/date.api.php +++ b/sites/all/modules/contrib/fields/date/date.api.php @@ -344,9 +344,6 @@ function hook_date_combo_process_alter(&$element, &$form_state, $context) { '#date_increment' => $instance['widget']['settings']['increment'], '#date_year_range' => $instance['widget']['settings']['year_range'], '#date_label_position' => $instance['widget']['settings']['label_position'], - '#prev_value' => isset($item['value']) ? $item['value'] : '', - '#prev_value2' => isset($item['value2']) ? $item['value2'] : '', - '#prev_rrule' => isset($item['rrule']) ? $item['rrule'] : '', '#date_repeat_widget' => str_replace('_repeat', '', $instance['widget']['type']), '#date_repeat_collapsed' => $instance['widget']['settings']['repeat_collapsed'], '#date_flexible' => 0, diff --git a/sites/all/modules/contrib/fields/date/date.devel_generate.inc b/sites/all/modules/contrib/fields/date/date.devel_generate.inc index 47de084..6c61e23 100644 --- a/sites/all/modules/contrib/fields/date/date.devel_generate.inc +++ b/sites/all/modules/contrib/fields/date/date.devel_generate.inc @@ -15,8 +15,14 @@ function date_devel_generate($entity, $field, $instance, $bundle) { $entity_field = array(); if (isset($instance['widget']['settings']['year_range'])) { $split = explode(':', $instance['widget']['settings']['year_range']); - $back = str_replace('-', '', $split[0]); - $forward = str_replace('+', '', $split[1]); + // Determine how much to go back and forward depending on whether a relative + // number of years (with - or + sign) or an absolute year is given. + $back = strpos($split[0], '-') === 0 + ? str_replace('-', '', $split[0]) + : date_format(date_now(), 'Y') - $split[0]; + $forward = strpos($split[1], '+') === 0 + ? str_replace('+', '', $split[1]) + : $split[1] - date_format(date_now(), 'Y'); } else { $back = 2; @@ -61,9 +67,11 @@ function date_devel_generate($entity, $field, $instance, $bundle) { case 'date': $format = DATE_FORMAT_ISO; break; + case 'datestamp': $format = DATE_FORMAT_UNIX; break; + case 'datetime': $format = DATE_FORMAT_DATETIME; break; diff --git a/sites/all/modules/contrib/fields/date/date.diff.inc b/sites/all/modules/contrib/fields/date/date.diff.inc new file mode 100644 index 0000000..e46d3a1 --- /dev/null +++ b/sites/all/modules/contrib/fields/date/date.diff.inc @@ -0,0 +1,79 @@ + $item) { + $date = date_formatter_process('date_default', $context['entity_type'], $context['entity'], $context['field'], $context['instance'], $context['language'], $item, $display); + switch ($display['settings']['fromto']) { + case 'both': + if ($date['value']['formatted'] != $date['value2']['formatted']) { + $diff_items[$delta] = t('@from to @to', array( + '@from' => $date['value']['formatted'], + '@to' => $date['value2']['formatted'], + )); + } + else { + $diff_items[$delta] = $date['value']['formatted']; + } + break; + + case 'value': + case 'value2': + $diff_items[$delta] = $date[$display['settings']['fromto']]['formatted']; + break; + + } + } + return $diff_items; +} + +/** + * Provide default field comparison options. + */ +function date_field_diff_default_options($field_type) { + return array( + 'format_type' => 'long', + 'fromto' => 'both', + ); +} + +/** + * Provide a form for setting the field comparison options. + */ +function date_field_diff_options_form($field_type, $settings) { + $options_form = array(); + + $form['format_type'] = array( + '#title' => t('Choose how render dates and times'), + '#type' => 'select', + '#options' => date_format_type_options(), + '#default_value' => $settings['format_type'], + '#description' => t('To add or edit options, visit Date and time settings.', array('@date-time-page' => url('admin/config/regional/date-time'))), + '#weight' => 0, + ); + + $form['fromto'] = array( + '#title' => t('Display'), + '#type' => 'select', + '#options' => array( + 'both' => t('Both Start and End dates'), + 'value' => t('Start date only'), + 'value2' => t('End date only'), + ), + '#default_value' => $settings['fromto'], + '#weight' => 1, + ); + + return $options_form; +} diff --git a/sites/all/modules/contrib/fields/date/date.field.inc b/sites/all/modules/contrib/fields/date/date.field.inc index b104702..7aef97b 100644 --- a/sites/all/modules/contrib/fields/date/date.field.inc +++ b/sites/all/modules/contrib/fields/date/date.field.inc @@ -19,6 +19,7 @@ function date_field_formatter_info() { 'multiple_from' => '', 'multiple_to' => '', 'fromto' => 'both', + 'show_remaining_days' => FALSE, ), ), 'format_interval' => array( @@ -27,6 +28,7 @@ function date_field_formatter_info() { 'settings' => array( 'interval' => 2, 'interval_display' => 'time ago', + 'use_end_date' => false, ), ), 'date_plain' => array( @@ -48,6 +50,7 @@ function date_field_formatter_settings_form($field, $instance, $view_mode, $form case 'format_interval': $form = date_interval_formatter_settings_form($field, $instance, $view_mode, $form, $form_state); break; + default: $form = date_default_formatter_settings_form($field, $instance, $view_mode, $form, $form_state); break; @@ -72,6 +75,7 @@ function date_field_formatter_settings_summary($field, $instance, $view_mode) { case 'format_interval': $summary = date_interval_formatter_settings_summary($field, $instance, $view_mode); break; + default: $summary = date_default_formatter_settings_summary($field, $instance, $view_mode); break; @@ -125,8 +129,16 @@ function date_field_formatter_view($entity_type, $entity, $field, $instance, $la 'attributes' => array(), 'rdf_mapping' => array(), 'add_rdf' => module_exists('rdf'), + 'microdata' => array(), + 'add_microdata' => module_exists('microdata'), ); + // If the microdata module is enabled, the microdata mapping will have been + // passed in via the entity. + if ($variables['add_microdata'] && isset($entity->microdata[$field['field_name']])) { + $variables['microdata'] = $entity->microdata[$field['field_name']]; + } + // If there is an RDf mapping for this date field, pass it down to the theme. $rdf_mapping = array(); if (!empty($entity->rdf_mapping) && function_exists('rdf_rdfa_attributes')) { @@ -161,11 +173,16 @@ function date_field_formatter_view($entity_type, $entity, $field, $instance, $la $element[$delta] = array('#markup' => $item['value']); } else { - $element[$delta] = array('#markup' => t('!start-date to !end-date', array('!start-date' => $item['value'], '!end-date' => $item['value2']))); + $element[$delta] = array( + '#markup' => t('!start-date to !end-date', array( + '!start-date' => $item['value'], + '!end-date' => $item['value2'] + ))); } } } break; + case 'format_interval': foreach ($items as $delta => $item) { if (!empty($entity->date_id) && !in_array($delta, $selected_deltas)) { @@ -180,6 +197,7 @@ function date_field_formatter_view($entity_type, $entity, $field, $instance, $la } } break; + default: foreach ($items as $delta => $item) { if (!empty($entity->date_id) && !in_array($delta, $selected_deltas)) { @@ -190,6 +208,7 @@ function date_field_formatter_view($entity_type, $entity, $field, $instance, $la $variables['item'] = $item; $variables['dates'] = date_formatter_process($formatter, $entity_type, $entity, $field, $instance, $langcode, $item, $display); $variables['attributes'] = !empty($rdf_mapping) ? rdf_rdfa_attributes($rdf_mapping, $item['value']) : array(); + $variables['show_remaining_days'] = isset($display['settings']['show_remaining_days']) ? $display['settings']['show_remaining_days'] : FALSE; $output = theme('date_display_combination', $variables); if (!empty($output)) { $element[$delta] = array('#markup' => $output); @@ -223,10 +242,11 @@ function date_field_is_empty($item, $field) { * Implements hook_field_info(). */ function date_field_info() { + $granularity = array('year', 'month', 'day', 'hour', 'minute'); $settings = array( 'settings' => array( 'todate' => '', - 'granularity' => drupal_map_assoc(array('year', 'month', 'day', 'hour', 'minute')), + 'granularity' => drupal_map_assoc($granularity), 'tz_handling' => 'site', 'timezone_db' => 'UTC', ), @@ -242,26 +262,26 @@ function date_field_info() { ); return array( 'datetime' => array( - 'label' => 'Date', + 'label' => t('Date'), 'description' => t('Store a date in the database as a datetime field, recommended for complete dates and times that may need timezone conversion.'), 'default_widget' => 'date_select', 'default_formatter' => 'date_default', 'default_token_formatter' => 'date_plain', - ) + $settings, + ) + $settings, 'date' => array( - 'label' => 'Date (ISO format)', + 'label' => t('Date (ISO format)'), 'description' => t('Store a date in the database as an ISO date, recommended for historical or partial dates.'), 'default_widget' => 'date_select', 'default_formatter' => 'date_default', 'default_token_formatter' => 'date_plain', - ) + $settings, + ) + $settings, 'datestamp' => array( - 'label' => 'Date (Unix timestamp)', + 'label' => t('Date (Unix timestamp)'), 'description' => t('Store a date in the database as a timestamp, deprecated format to support legacy data.'), 'default_widget' => 'date_select', 'default_formatter' => 'date_default', 'default_token_formatter' => 'date_plain', - ) + $settings, + ) + $settings, ); } @@ -286,24 +306,24 @@ function date_field_widget_info() { $info = array( 'date_select' => array( - 'label' => t('Select list'), + 'label' => t('Select list'), 'field types' => array('date', 'datestamp', 'datetime'), ) + $settings, 'date_text' => array( - 'label' => t('Text field'), + 'label' => t('Text field'), 'field types' => array('date', 'datestamp', 'datetime'), - ) + $settings, + ) + $settings, ); if (module_exists('date_popup')) { $info['date_popup'] = array( - 'label' => t('Pop-up calendar'), + 'label' => t('Pop-up calendar'), 'field types' => array('date', 'datestamp', 'datetime'), ) + $settings; } // The date text widget should use an increment of 1. - $info['date_text']['increment'] = 1; + $info['date_text']['settings']['increment'] = 1; return $info; } @@ -443,6 +463,14 @@ function date_field_instance_settings_form($field, $instance) { return _date_field_instance_settings_form($field, $instance); } +/** + * Form validation handler for _date_field_instance_settings_form(). + */ +function date_field_instance_settings_form_validate(&$form, &$form_state) { + module_load_include('inc', 'date', 'date_admin'); + return _date_field_instance_settings_form_validate($form, $form_state); +} + /** * Implements hook_field_widget_settings_form(). */ @@ -451,6 +479,14 @@ function date_field_widget_settings_form($field, $instance) { return _date_field_widget_settings_form($field, $instance); } +/** + * Form validation handler for _date_field_widget_settings_form(). + */ +function date_field_widget_settings_form_validate(&$form, &$form_state) { + module_load_include('inc', 'date', 'date_admin'); + return _date_field_widget_settings_form_validate($form, $form_state); +} + /** * Implements hook_field_settings_form(). */ @@ -459,6 +495,14 @@ function date_field_settings_form($field, $instance, $has_data) { return _date_field_settings_form($field, $instance, $has_data); } +/** + * Form validation handler for _date_field_settings_form(). + */ +function date_field_settings_validate(&$form, &$form_state) { + module_load_include('inc', 'date', 'date_admin'); + return _date_field_settings_validate($form, $form_state); +} + /** * Implements hook_content_migrate_field_alter(). * diff --git a/sites/all/modules/contrib/fields/date/date.info b/sites/all/modules/contrib/fields/date/date.info index e5b00c2..28263b4 100644 --- a/sites/all/modules/contrib/fields/date/date.info +++ b/sites/all/modules/contrib/fields/date/date.info @@ -4,15 +4,20 @@ dependencies[] = date_api package = Date/Time core = 7.x php = 5.2 +files[] = date.migrate.inc files[] = tests/date_api.test files[] = tests/date.test files[] = tests/date_field.test +files[] = tests/date_migrate.test files[] = tests/date_validation.test files[] = tests/date_timezone.test +files[] = tests/date_views_pager.test +files[] = tests/date_views_popup.test +files[] = tests/date_form.test -; Information added by drupal.org packaging script on 2012-08-13 -version = "7.x-2.6" +; Information added by Drupal.org packaging script on 2017-04-07 +version = "7.x-2.10" core = "7.x" project = "date" -datestamp = "1344850024" +datestamp = "1491562090" diff --git a/sites/all/modules/contrib/fields/date/date.install b/sites/all/modules/contrib/fields/date/date.install index 23fb07e..765b90c 100644 --- a/sites/all/modules/contrib/fields/date/date.install +++ b/sites/all/modules/contrib/fields/date/date.install @@ -19,6 +19,7 @@ function date_field_schema($field) { 'views' => TRUE, ); break; + case 'datetime': $db_columns['value'] = array( 'type' => 'datetime', @@ -31,6 +32,7 @@ function date_field_schema($field) { 'views' => TRUE, ); break; + default: $db_columns['value'] = array( 'type' => 'varchar', @@ -66,7 +68,12 @@ function date_field_schema($field) { 'views' => FALSE, ); if (!empty($field['settings']['todate'])) { - $db_columns['offset2'] = array('type' => 'int', 'not null' => FALSE, 'sortable' => TRUE, 'views' => FALSE); + $db_columns['offset2'] = array( + 'type' => 'int', + 'not null' => FALSE, + 'sortable' => TRUE, + 'views' => FALSE + ); } } if (isset($field['settings']['repeat']) && $field['settings']['repeat'] == 1) { @@ -88,8 +95,9 @@ function date_update_last_removed() { } /** - * Get rid of the individual formatters for each format type, - * these are now settings in the default formatter. + * Get rid of the individual formatters for each format type. + * + * These are now settings in the default formatter. */ function date_update_7000() { $instances = field_info_instances(); @@ -115,8 +123,9 @@ function date_update_7000() { } /** - * Get rid of the separate widgets for repeating dates. The code now handles - * repeating dates correctly using the regular widgets. + * Get rid of the separate widgets for repeating dates. + * + * The code now handles repeating dates correctly using the regular widgets. */ function date_update_7001() { $query = db_select('field_config_instance', 'fci', array('fetch' => PDO::FETCH_ASSOC)); @@ -127,7 +136,11 @@ function date_update_7001() { foreach ($results as $record) { $instance = unserialize($record['data']); - if (in_array($instance['widget']['type'], array('date_popup_repeat', 'date_text_repeat', 'date_select_repeat'))) { + if (in_array($instance['widget']['type'], array( + 'date_popup_repeat', + 'date_text_repeat', + 'date_select_repeat' + ))) { $instance['widget']['type'] = str_replace('_repeat', '', $instance['widget']['type']); db_update('field_config_instance') ->fields(array( @@ -192,3 +205,10 @@ function date_update_7004() { drupal_set_message(t('Date text widgets have been updated to use an increment of 1.')); } +/** + * Revisited: Date text widgets should always use an increment of 1. + */ +function date_update_7005() { + // @see https://www.drupal.org/node/1355256 + date_update_7004(); +} diff --git a/sites/all/modules/contrib/fields/date/date.js b/sites/all/modules/contrib/fields/date/date.js index 2e60175..b58d3be 100644 --- a/sites/all/modules/contrib/fields/date/date.js +++ b/sites/all/modules/contrib/fields/date/date.js @@ -27,7 +27,7 @@ Drupal.date.EndDateHandler = function (widget) { this.$widget = $(widget); this.$start = this.$widget.find('.form-type-date-select[class$=value]'); this.$end = this.$widget.find('.form-type-date-select[class$=value2]'); - if (this.$end.length == 0) { + if (this.$end.length === 0) { return; } this.initializeSelects(); @@ -68,7 +68,7 @@ Drupal.date.EndDateHandler.prototype.endDateIsBlank = function () { var id; for (id in this.selects) { if (this.selects.hasOwnProperty(id)) { - if (this.selects[id].end.val() != '') { + if (this.selects[id].end.val() !== '') { return false; } } diff --git a/sites/all/modules/contrib/fields/date/date_migrate/date.migrate.inc b/sites/all/modules/contrib/fields/date/date.migrate.inc similarity index 71% rename from sites/all/modules/contrib/fields/date/date_migrate/date.migrate.inc rename to sites/all/modules/contrib/fields/date/date.migrate.inc index 637a626..90c1ec5 100644 --- a/sites/all/modules/contrib/fields/date/date_migrate/date.migrate.inc +++ b/sites/all/modules/contrib/fields/date/date.migrate.inc @@ -1,10 +1,24 @@ 2, + 'field handlers' => array('DateMigrateFieldHandler'), + ); + return $api; +} + class DateMigrateFieldHandler extends MigrateFieldHandler { /** @@ -29,7 +43,7 @@ class DateMigrateFieldHandler extends MigrateFieldHandler { * @return array * An array of the defined variables in this scope. */ - static function arguments($timezone = 'UTC', $timezone_db = 'UTC', $rrule = NULL, $language = NULL) { + public static function arguments($timezone = 'UTC', $timezone_db = 'UTC', $rrule = NULL, $language = NULL) { return get_defined_vars(); } @@ -57,37 +71,47 @@ class DateMigrateFieldHandler extends MigrateFieldHandler { $arguments = array(); } - if (isset($arguments['timezone'])) { - $default_timezone = $arguments['timezone']; - } - else { - $default_timezone = 'UTC'; - } - if (isset($arguments['timezone_db'])) { - $default_timezone_db = $arguments['timezone_db']; - } - else { - $default_timezone_db = NULL; - } - if (isset($arguments['rrule'])) { - $default_rrule = $arguments['rrule']; - } - else { - $default_rrule = NULL; - } $language = $this->getFieldLanguage($entity, $field_info, $arguments); - // Setup the standard Field API array for saving. - $delta = 0; - foreach ($values as $from) { - // Set defaults. - $to = NULL; - $timezone = $default_timezone; - $timezone_db = $default_timezone_db; - $rrule = $default_rrule; + foreach ($values as $delta => $from) { + if (!empty($arguments['timezone'])) { + if (is_array($arguments['timezone'])) { + $timezone = $arguments['timezone'][$delta]; + } + else { + $timezone = $arguments['timezone']; + } + } + else { + $timezone = 'UTC'; + } - // Is the value a straight datetime value, or JSON containing a set of - // properties? + if (!empty($arguments['rrule'])) { + if (is_array($arguments['rrule'])) { + $rrule = $arguments['rrule'][$delta]; + } + else { + $rrule = $arguments['rrule']; + } + } + else { + $rrule = NULL; + } + + if (!empty($arguments['to'])) { + if (is_array($arguments['to'])) { + $to = $arguments['to'][$delta]; + } + else { + $to = $arguments['to']; + } + } + else { + $to = NULL; + } + + // Legacy support for JSON containing a set of properties - deprecated + // now that we have subfields. if (!empty($from) && $from{0} == '{') { $properties = drupal_json_decode($from); $from = $properties['from']; @@ -98,9 +122,6 @@ class DateMigrateFieldHandler extends MigrateFieldHandler { if (!empty($properties['timezone'])) { $timezone = $properties['timezone']; } - if (!empty($properties['timezone_db'])) { - $timezone_db = $properties['timezone_db']; - } if (!empty($properties['rrule'])) { $rrule = $properties['rrule']; } @@ -111,6 +132,7 @@ class DateMigrateFieldHandler extends MigrateFieldHandler { // timestamp for 'now'. if (empty($from)) { $return[$language][$delta]['value'] = NULL; + $return[$language][$delta]['timezone'] = NULL; if (!empty($field_info['settings']['todate'])) { $return[$language][$delta]['value2'] = NULL; } @@ -133,6 +155,7 @@ class DateMigrateFieldHandler extends MigrateFieldHandler { case 'datestamp': // Already done. break; + case 'datetime': // YYYY-MM-DD HH:MM:SS. $from = format_date($from, 'custom', 'Y-m-d H:i:s', $timezone); @@ -140,6 +163,7 @@ class DateMigrateFieldHandler extends MigrateFieldHandler { $to = format_date($to, 'custom', 'Y-m-d H:i:s', $timezone); } break; + case 'date': // ISO date: YYYY-MM-DDTHH:MM:SS. $from = format_date($from, 'custom', 'Y-m-d\TH:i:s', $timezone); @@ -147,6 +171,7 @@ class DateMigrateFieldHandler extends MigrateFieldHandler { $to = format_date($to, 'custom', 'Y-m-d\TH:i:s', $timezone); } break; + default: break; } @@ -155,21 +180,36 @@ class DateMigrateFieldHandler extends MigrateFieldHandler { // created. if (function_exists('date_repeat_build_dates') && !empty($field_info['settings']['repeat']) && $rrule) { include_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'date_api') . '/date_api_ical.inc'; - $item = array('value' => $from, 'value2' => $to, 'timezone' => $timezone); + $item = array( + 'value' => $from, + 'value2' => $to, + 'timezone' => $timezone, + ); // Can be de-uglified when http://drupal.org/node/1159404 is committed. $return[$language] = date_repeat_build_dates(NULL, date_ical_parse_rrule($field_info, $rrule), $field_info, $item); } else { $return[$language][$delta]['value'] = $from; + $return[$language][$delta]['timezone'] = $timezone; if (!empty($to)) { $return[$language][$delta]['value2'] = $to; } } - $delta++; } if (!isset($return)) { $return = NULL; } return $return; } + + /** + * {@inheritdoc} + */ + public function fields($migration = NULL) { + return array( + 'timezone' => t('Timezone'), + 'rrule' => t('Recurring event rule'), + 'to' => t('End date date'), + ); + } } diff --git a/sites/all/modules/contrib/fields/date/date.module b/sites/all/modules/contrib/fields/date/date.module index 4f6a013..e667b5d 100644 --- a/sites/all/modules/contrib/fields/date/date.module +++ b/sites/all/modules/contrib/fields/date/date.module @@ -17,6 +17,7 @@ function date_get_entity_bundle($entity_type, $entity) { case 'field_collection_item': $bundle = $entity->field_name; break; + default: $bundle = field_extract_bundle($entity_type, $entity); break; @@ -42,13 +43,20 @@ function date_default_format($type) { * Wrapper function around each of the widget types for creating a date object. */ function date_input_date($field, $instance, $element, $input) { + // Trim extra spacing off user input of text fields. + if (isset($input['date'])) { + $input['date'] = trim($input['date']); + } + switch ($instance['widget']['type']) { case 'date_text': $function = 'date_text_input_date'; break; + case 'date_popup': $function = 'date_popup_input_date'; break; + default: $function = 'date_select_input_date'; } @@ -68,6 +76,7 @@ function date_theme() { ); $themes = array( 'date_combo' => $base + array('render element' => 'element'), + 'date_form_element' => $base + array('render element' => 'element'), 'date_text_parts' => $base + array('render element' => 'element'), 'date' => $base + array('render element' => 'element'), 'date_display_single' => $base + array( @@ -78,6 +87,8 @@ function date_theme() { 'attributes' => array(), 'rdf_mapping' => NULL, 'add_rdf' => NULL, + 'microdata' => NULL, + 'add_microdata' => NULL, ), ), 'date_display_range' => $base + array( @@ -95,7 +106,15 @@ function date_theme() { 'attributes_end' => array(), 'rdf_mapping' => NULL, 'add_rdf' => NULL, - )), + 'microdata' => NULL, + 'add_microdata' => NULL, + ), + ), + 'date_display_remaining' => $base + array( + 'variables' => array( + 'remaining_days' => NULL, + ), + ), 'date_display_combination' => $base + array( 'variables' => array( 'entity_type' => NULL, @@ -110,6 +129,8 @@ function date_theme() { 'attributes' => array(), 'rdf_mapping' => NULL, 'add_rdf' => NULL, + 'microdata' => NULL, + 'add_microdata' => NULL, ), ), 'date_display_interval' => $base + array( @@ -126,7 +147,7 @@ function date_theme() { 'attributes' => array(), 'rdf_mapping' => NULL, 'add_rdf' => NULL, - ), + ), ), ); @@ -205,8 +226,10 @@ function date_formatter_process($formatter, $entity_type, $entity, $field, $inst $settings = $display['settings']; $field_name = $field['field_name']; $format = date_formatter_format($formatter, $settings, $granularity, $langcode); - $timezone = isset($item['timezone']) ? $item['timezone'] : ''; - $timezone = date_get_timezone($field['settings']['tz_handling'], $timezone); + if (!isset($field['settings']['tz_handling']) || $field['settings']['tz_handling'] !== 'utc') { + $timezone = isset($item['timezone']) ? $item['timezone'] : ''; + $timezone = date_get_timezone($field['settings']['tz_handling'], $timezone); + } $timezone_db = date_get_timezone_db($field['settings']['tz_handling']); $db_format = date_type_format($field['type']); $process = date_process_values($field); @@ -242,22 +265,23 @@ function date_formatter_process($formatter, $entity_type, $entity, $field, $inst $dates[$processed]['formatted_iso'] = date_format_date($date, 'custom', 'c'); if (is_object($date)) { if ($format == 'format_interval') { - $dates[$processed]['interval'] = date_format_interval($date); + $dates[$processed]['interval'] = date_format_interval($date); } elseif ($format == 'format_calendar_day') { - $dates[$processed]['calendar_day'] = date_format_calendar_day($date); + $dates[$processed]['calendar_day'] = date_format_calendar_day($date); } - elseif ($format == 'U') { + elseif ($format == 'U' || $format == 'r' || $format == 'c') { $dates[$processed]['formatted'] = date_format_date($date, 'custom', $format); $dates[$processed]['formatted_date'] = date_format_date($date, 'custom', $format); $dates[$processed]['formatted_time'] = ''; $dates[$processed]['formatted_timezone'] = ''; } elseif (!empty($format)) { - $dates[$processed]['formatted'] = date_format_date($date, 'custom', $format); - $dates[$processed]['formatted_date'] = date_format_date($date, 'custom', date_limit_format($format, array('year', 'month', 'day'))); - $dates[$processed]['formatted_time'] = date_format_date($date, 'custom', date_limit_format($format, array('hour', 'minute', 'second'))); - $dates[$processed]['formatted_timezone'] = date_format_date($date, 'custom', date_limit_format($format, array('timezone'))); + $formats = _get_custom_date_format($date, $format); + $dates[$processed]['formatted'] = $formats['formatted']; + $dates[$processed]['formatted_date'] = $formats['date']; + $dates[$processed]['formatted_time'] = $formats['time']; + $dates[$processed]['formatted_timezone'] = $formats['zone']; } } } @@ -284,6 +308,30 @@ function date_formatter_process($formatter, $entity_type, $entity, $field, $inst return $dates; } +/** + * Get a custom date format. + */ +function _get_custom_date_format($date, $format) { + $custom = array(); + $custom['granularities'] = array( + 'date' => array('year', 'month', 'day'), + 'time' => array('hour', 'minute', 'second'), + 'zone' => array('timezone'), + ); + $custom['limits'] = array( + 'date' => date_limit_format($format, $custom['granularities']['date']), + 'time' => date_limit_format($format, $custom['granularities']['time']), + 'zone' => date_limit_format($format, $custom['granularities']['zone']), + ); + + return array( + 'formatted' => date_format_date($date, 'custom', $format), + 'date' => date_format_date($date, 'custom', $custom['limits']['date']), + 'time' => date_format_date($date, 'custom', $custom['limits']['time']), + 'zone' => date_format_date($date, 'custom', $custom['limits']['zone']), + ); +} + /** * Retrieves the granularity for a field. * @@ -297,14 +345,14 @@ function date_formatter_process($formatter, $entity_type, $entity, $field, $inst */ function date_granularity($field) { if (!is_array($field) || !is_array($field['settings']['granularity'])) { - $field['settings']['granularity'] = drupal_map_assoc(array('year', 'month', 'day')); + $granularity = drupal_map_assoc(array('year', 'month', 'day')); + $field['settings']['granularity'] = $granularity; } return array_values(array_filter($field['settings']['granularity'])); } /** - * Helper function to create an array of the date values in a - * field that need to be processed. + * Helper function to create an array of the date values in a field that need to be processed. */ function date_process_values($field) { return $field['settings']['todate'] ? array('value', 'value2') : array('value'); @@ -390,12 +438,17 @@ function date_formatter_format($formatter, $settings, $granularity = array(), $l switch ($formatter) { case 'format_interval': return 'format_interval'; - break; + case 'date_plain': return 'date_plain'; - break; + default: - $format = date_format_type_format($format_type, $langcode); + if ($format_type == 'custom') { + $format = $settings['custom_date_format']; + } + else { + $format = date_format_type_format($format_type, $langcode); + } break; } @@ -406,6 +459,7 @@ function date_formatter_format($formatter, $settings, $granularity = array(), $l /** * Helper function to get the right format for a format type. + * * Checks for locale-based format first. */ function date_format_type_format($format_type, $langcode = NULL) { @@ -428,27 +482,30 @@ function date_format_type_format($format_type, $langcode = NULL) { case 'short': $default = 'm/d/Y - H:i'; break; + case 'long': $default = 'l, F j, Y - H:i'; break; + // If it's not one of the core date types and isn't stored in the // database, we'll fall back on using the same default format as the // 'medium' type. case 'medium': default: // @todo: If a non-core module provides a date type and does not - // variable_set() a default for it, the default assumed here may - // not be correct (since the default format used by 'medium' may - // not even be one of the allowed formats for the date type in - // question). To fix this properly, we should really call - // system_get_date_formats($format_type) and take the first - // format from that list as the default. However, this function - // is called often (on many different page requests), so calling - // system_get_date_formats() from here would be a performance hit - // since that function writes several records to the database - // during each page request that calls it. + // variable_set() a default for it, the default assumed here may + // not be correct (since the default format used by 'medium' may + // not even be one of the allowed formats for the date type in + // question). To fix this properly, we should really call + // system_get_date_formats($format_type) and take the first + // format from that list as the default. However, this function + // is called often (on many different page requests), so calling + // system_get_date_formats() from here would be a performance hit + // since that function writes several records to the database + // during each page request that calls it. $default = 'D, m/d/Y - H:i'; break; + } $format = variable_get('date_format_' . $format_type, $default); } @@ -502,7 +559,7 @@ function date_prepare_entity($formatter, $entity_type, $entity, $field, $instanc elseif ((!empty($max_count) && is_numeric($max_count) && $count >= $max_count) || (!empty($value['value']) && $value['value'] < $start) || (!empty($value['value2']) && $value['value2'] > $end)) { - unset($entity->{$field_name}[$langcode][$delta]); + unset($entity->{$field_name}[$langcode][$delta]); } else { $count++; @@ -532,6 +589,7 @@ function date_entity_metadata_property_info_alter(&$info, $entity_type, $field, if (!empty($field['settings']['todate'])) { // Define a simple data structure containing both dates. $property['type'] = ($field['cardinality'] != 1) ? 'list' : 'struct'; + $property['auto creation'] = 'date_entity_metadata_struct_create'; $property['getter callback'] = 'entity_metadata_field_verbatim_get'; $property['setter callback'] = 'entity_metadata_field_verbatim_set'; $property['property info'] = array( @@ -543,6 +601,8 @@ function date_entity_metadata_property_info_alter(&$info, $entity_type, $field, // The getter and setter callbacks for 'value' and 'value2' // will not provide the field name as $name, we'll add it to $info. 'field_name' => $field['field_name'], + // Alert Microdata module that this value can be exposed in microdata. + 'microdata' => TRUE, ), 'value2' => array( 'type' => 'date', @@ -552,6 +612,8 @@ function date_entity_metadata_property_info_alter(&$info, $entity_type, $field, // The getter and setter callbacks for 'value' and 'value2' // will not provide the field name as $name, we'll add it to $info. 'field_name' => $field['field_name'], + // Alert Microdata module that this value can be exposed in microdata. + 'microdata' => TRUE, ), 'duration' => array( 'type' => 'duration', @@ -566,6 +628,11 @@ function date_entity_metadata_property_info_alter(&$info, $entity_type, $field, ); unset($property['query callback']); } + else { + // If this doesn't have a todate, it is handled as a date rather than a + // struct. Enable microdata on the field itself rather than the properties. + $property['microdata'] = TRUE; + } } /** @@ -632,12 +699,22 @@ function date_entity_metadata_field_setter(&$entity, $name, $value, $langcode, $ drupal_static_reset('field_language'); } +/** + * Auto creation callback for fields which contain two date values in one. + */ +function date_entity_metadata_struct_create($name, $property_info) { + return array( + 'date_type' => $property_info['field']['columns'][$name]['type'], + 'timezone_db' => $property_info['field']['settings']['timezone_db'], + ); +} + /** * Callback for setting an individual field value if a to-date may be there too. + * * Based on entity_property_verbatim_set(). * - * The passed in unix timestamp (UTC) is converted to the right value and - * format dependent on the field. + * The passed in unix timestamp (UTC) is converted to the right value and format dependent on the field. * * $name is either 'value' or 'value2'. */ @@ -659,9 +736,9 @@ function date_entity_metadata_struct_setter(&$item, $name, $value, $langcode, $t } /** - * Duplicate functionality of what is now date_all_day_field() in - * the Date All Day module. Copy left here to avoid breaking other - * modules that use this function. + * Duplicate functionality of what is now date_all_day_field() in the Date All Day module. + * + * Copy left here to avoid breaking other modules that use this function. * * DEPRECATED!, will be removed at some time in the future. */ @@ -735,7 +812,7 @@ function date_field_widget_properties_alter(&$widget, $context) { $entity = $context['entity']; $info = entity_get_info($entity_type); $id = $info['entity keys']['id']; - $widget['is_new']= FALSE; + $widget['is_new'] = FALSE; if (empty($entity->$id)) { $widget['is_new'] = TRUE; } diff --git a/sites/all/modules/contrib/fields/date/date.theme b/sites/all/modules/contrib/fields/date/date.theme index 70c6be4..c4ea352 100644 --- a/sites/all/modules/contrib/fields/date/date.theme +++ b/sites/all/modules/contrib/fields/date/date.theme @@ -74,7 +74,10 @@ function theme_date_display_combination($variables) { $attributes = $variables['attributes']; $rdf_mapping = $variables['rdf_mapping']; $add_rdf = $variables['add_rdf']; + $microdata = $variables['microdata']; + $add_microdata = $variables['add_microdata']; $precision = date_granularity_precision($field['settings']['granularity']); + $show_remaining_days = $variables['show_remaining_days']; $output = ''; @@ -119,10 +122,12 @@ function theme_date_display_combination($variables) { $date1 = $dates['value']['formatted']; $date2 = $date1; break; + case 'value2': $date2 = $dates['value2']['formatted']; $date1 = $date2; break; + default: $date1 = $dates['value']['formatted']; $date2 = $dates['value2']['formatted']; @@ -149,6 +154,20 @@ function theme_date_display_combination($variables) { $has_time_string = FALSE; } + // Check remaining days. + $show_remaining_days = ''; + if (!empty($variables['show_remaining_days'])) { + $remaining_days = floor((strtotime($variables['dates']['value']['formatted_iso']) + - strtotime('now')) / (24 * 3600)); + + // Show remaining days only for future events. + if ($remaining_days >= 0) { + $show_remaining_days = theme('date_display_remaining', array( + 'remaining_days' => $remaining_days, + )); + } + } + // No date values, display nothing. if (empty($date1) && empty($date2)) { $output .= ''; @@ -162,7 +181,10 @@ function theme_date_display_combination($variables) { 'attributes' => $attributes, 'rdf_mapping' => $rdf_mapping, 'add_rdf' => $add_rdf, + 'microdata' => $microdata, + 'add_microdata' => $add_microdata, 'dates' => $dates, + 'show_remaining_days' => $show_remaining_days, )); } // Same day, different times, don't repeat the date but show both Start and @@ -179,7 +201,10 @@ function theme_date_display_combination($variables) { 'attributes' => $attributes, 'rdf_mapping' => $rdf_mapping, 'add_rdf' => $add_rdf, + 'microdata' => $microdata, + 'add_microdata' => $add_microdata, 'dates' => $dates, + 'show_remaining_days' => $show_remaining_days, )); $replaced = str_replace($time1, $time, $date1); $output .= theme('date_display_single', array( @@ -200,7 +225,10 @@ function theme_date_display_combination($variables) { 'attributes' => $attributes, 'rdf_mapping' => $rdf_mapping, 'add_rdf' => $add_rdf, + 'microdata' => $microdata, + 'add_microdata' => $add_microdata, 'dates' => $dates, + 'show_remaining_days' => $show_remaining_days, )); } @@ -211,7 +239,7 @@ function theme_date_display_combination($variables) { * Template preprocess function for displaying a single date. */ function template_preprocess_date_display_single(&$variables) { - if ($variables['add_rdf']) { + if ($variables['add_rdf'] || !empty($variables['add_microdata'])) { // Pass along the rdf mapping for this field, if any. Add some default rdf // attributes that will be used if not overridden by attributes passed in. $rdf_mapping = $variables['rdf_mapping']; @@ -222,6 +250,24 @@ function template_preprocess_date_display_single(&$variables) { ); $variables['attributes'] = $variables['attributes'] + $base_attributes; } + + // Pass along microdata attributes, or set display to false if none are set. + if (!empty($variables['add_microdata'])) { + // Because the Entity API integration for Date has a variable data + // structure depending on whether there is an end value, the attributes + // could be attached to the field or to the value property. + if (!empty($variables['microdata']['#attributes']['itemprop'])) { + $variables['microdata']['value']['#attributes'] = $variables['microdata']['#attributes']; + } + + // Add the machine readable time using the content attribute. + if (!empty($variables['microdata']['value']['#attributes'])) { + $variables['microdata']['value']['#attributes']['content'] = $variables['dates']['value']['formatted_iso']; + } + else { + $variables['add_microdata'] = FALSE; + } + } } /** @@ -231,9 +277,17 @@ function theme_date_display_single($variables) { $date = $variables['date']; $timezone = $variables['timezone']; $attributes = $variables['attributes']; + $show_remaining_days = isset($variables['show_remaining_days']) ? $variables['show_remaining_days'] : ''; // Wrap the result with the attributes. - return '' . $date . $timezone . ''; + $output = '' . $date . $timezone . ''; + + if (!empty($variables['add_microdata'])) { + $output .= ''; + } + + // Add remaining message and return. + return $output . $show_remaining_days; } /** @@ -247,7 +301,6 @@ function template_preprocess_date_display_range(&$variables) { if ($variables['add_rdf']) { // Pass along the rdf mapping for this field, if any. Add some default rdf // attributes that will be used if not overridden by attributes passed in. - $rdf_mapping = $variables['rdf_mapping']; $dates = $variables['dates']; $base_attributes = array( 'property' => array('dc:date'), @@ -261,6 +314,17 @@ function template_preprocess_date_display_range(&$variables) { $variables['attributes_end']['property'][$delta] = str_replace('start', 'end', $property); } } + + // Pass along microdata attributes, or set display to false if none are set. + if ($variables['add_microdata']) { + if (!empty($variables['microdata']['value']['#attributes'])) { + $variables['microdata']['value']['#attributes']['content'] = $variables['dates']['value']['formatted_iso']; + $variables['microdata']['value2']['#attributes']['content'] = $variables['dates']['value2']['formatted_iso']; + } + else { + $variables['add_microdata'] = FALSE; + } + } } /** @@ -272,12 +336,26 @@ function theme_date_display_range($variables) { $timezone = $variables['timezone']; $attributes_start = $variables['attributes_start']; $attributes_end = $variables['attributes_end']; + $show_remaining_days = $variables['show_remaining_days']; + + $start_date = '' . $date1 . ''; + $end_date = '' . $date2 . $timezone . ''; + + // If microdata attributes for the start date property have been passed in, + // add the microdata in meta tags. + if (!empty($variables['add_microdata'])) { + $start_date .= ''; + $end_date .= ''; + } // Wrap the result with the attributes. - return t('!start-date to !end-date', array( - '!start-date' => '' . $date1 . '', - '!end-date' => '' . $date2 . $timezone . '', - )); + $output = '' . t('!start-date to !end-date', array( + '!start-date' => $start_date, + '!end-date' => $end_date, + )) . ''; + + // Add remaining message and return. + return $output . $show_remaining_days; } /** @@ -300,6 +378,8 @@ function theme_date_display_interval($variables) { 'end_date' => $dates['value2']['local']['object'], 'interval' => $options['interval'], 'interval_display' => $options['interval_display'], + 'use_end_date' => !empty($options['use_end_date']) ? + $options['use_end_date'] : FALSE, ); if ($return = theme('date_time_ago', $time_ago_vars)) { @@ -320,12 +400,16 @@ function theme_date_combo($variables) { // Group start/end items together in fieldset. $fieldset = array( - '#title' => t($element['#title']) . ' ' . ($element['#delta'] > 0 ? intval($element['#delta'] + 1) : ''), + '#title' => field_filter_xss(t($element['#title'])) . ($element['#delta'] > 0 ? ' ' . intval($element['#delta'] + 1) : ''), '#value' => '', - '#description' => !empty($element['#fieldset_description']) ? $element['#fieldset_description'] : '', - '#attributes' => array(), + '#description' => !empty($element['#description']) ? $element['#description'] : '', + '#attributes' => array('class' => array('date-combo')), '#children' => $element['#children'], ); + // Add marker to required date fields. + if ($element['#required']) { + $fieldset['#title'] .= " " . theme('form_required_marker'); + } return theme('fieldset', array('element' => $fieldset)); } @@ -340,7 +424,11 @@ function theme_date_text_parts($variables) { $rows[] = drupal_render($element[$key]); } else { - $rows[] = array($part, drupal_render($element[$key][0]), drupal_render($element[$key][1])); + $rows[] = array( + $part, + drupal_render($element[$key][0]), + drupal_render($element[$key][1]), + ); } } if ($element['year']['#type'] == 'hidden') { @@ -352,4 +440,49 @@ function theme_date_text_parts($variables) { } } +/** + * Render a date combo as a form element. + */ +function theme_date_form_element($variables) { + $element = &$variables['element']; + + // Detect whether element is multiline. + $count = preg_match_all('`<(?:div|span)\b[^>]* class="[^"]*\b(?:date-no-float|date-clear)\b`', $element['#children'], $matches, PREG_OFFSET_CAPTURE); + $multiline = FALSE; + if ($count > 1) { + $multiline = TRUE; + } + elseif ($count) { + $before = substr($element['#children'], 0, $matches[0][0][1]); + if (preg_match('`<(?:div|span)\b[^>]* class="[^"]*\bdate-float\b`', $before)) { + $multiline = TRUE; + } + } + + // Detect if there is more than one subfield. + $count = count(explode(''. $element['#children'] .''; + + return theme('form_element', $variables); +} + +/** + * Returns HTML for remaining message. + */ +function theme_date_display_remaining($variables) { + $remaining_days = $variables['remaining_days']; + $output = ''; + $show_remaining_text = t('The upcoming date less then 1 day.'); + if ($remaining_days) { + $show_remaining_text = format_plural($remaining_days, 'To event remaining 1 day', 'To event remaining @count days'); + } + + return '
    ' . $show_remaining_text . '
    '; +} + /** @} End of addtogroup themeable */ diff --git a/sites/all/modules/contrib/fields/date/date_admin.inc b/sites/all/modules/contrib/fields/date/date_admin.inc index 993aa09..b800201 100644 --- a/sites/all/modules/contrib/fields/date/date_admin.inc +++ b/sites/all/modules/contrib/fields/date/date_admin.inc @@ -14,15 +14,24 @@ function date_default_formatter_settings_form($field, $instance, $view_mode, $fo $formatter = $display['type']; $form = array(); + $date_formats = date_format_type_options(); $form['format_type'] = array( '#title' => t('Choose how users view dates and times:'), '#type' => 'select', - '#options' => date_format_type_options(), + '#options' => $date_formats + array('custom' => t('Custom')), '#default_value' => $settings['format_type'], '#description' => t('To add or edit options, visit Date and time settings.', array('@date-time-page' => url('admin/config/regional/date-time'))), '#weight' => 0, ); + $form['custom_date_format'] = array( + '#type' => 'textfield', + '#title' => t('Custom date format'), + '#description' => t('If "Custom", see the PHP manual for date formats. Otherwise, enter the number of different time units to display, which defaults to 2.', array('@url' => 'http://php.net/manual/function.date.php')), + '#default_value' => isset($settings['custom_date_format']) ? $settings['custom_date_format'] : '', + '#dependency' => array('edit-options-settings-format-type' => array('custom')), + ); + $form['fromto'] = array( '#title' => t('Display:'), '#type' => 'select', @@ -74,6 +83,12 @@ function date_default_formatter_settings_form($field, $instance, $view_mode, $fo '#description' => t('Identify specific start and/or end dates in the format YYYY-MM-DDTHH:MM:SS, or leave blank for all available dates.'), ); + $form['show_remaining_days'] = array( + '#title' => t('Show remaining days'), + '#type' => 'checkbox', + '#default_value' => $settings['show_remaining_days'], + '#weight' => 0, + ); return $form; } @@ -110,6 +125,14 @@ function date_interval_formatter_settings_form($field, $instance, $view_mode, $f '#default_value' => $settings['interval_display'], '#weight' => 0, ); + if (!empty($field['settings']['todate'])) { + $form['use_end_date'] = array( + '#title' => t('Use End date'), + '#description' => 'Use the End date, instead of the start date', + '#type' => 'checkbox', + '#default_value' => $settings['use_end_date'], + ); + } return $form; } @@ -127,9 +150,11 @@ function date_default_formatter_settings_summary($field, $instance, $view_mode) case 'date_plain': $format = t('Plain'); break; + case 'format_interval': $format = t('Interval'); break; + default: if (!empty($format_types[$settings['format_type']])) { $format = $format_types[$settings['format_type']]; @@ -148,7 +173,9 @@ function date_default_formatter_settings_summary($field, $instance, $view_mode) 'value' => t('Display Start date only'), 'value2' => t('Display End date only'), ); - $summary[] = $options[$settings['fromto']]; + if (isset($options[$settings['fromto']])) { + $summary[] = $options[$settings['fromto']]; + } } if (array_key_exists('multiple_number', $settings) && !empty($field['cardinality'])) { @@ -159,6 +186,10 @@ function date_default_formatter_settings_summary($field, $instance, $view_mode) )); } + if (array_key_exists('show_remaining_days', $settings)) { + $summary[] = t('Show remaining days: @value', array('@value' => ($settings['show_remaining_days'] ? 'yes' : 'no'))); + } + return $summary; } @@ -172,7 +203,9 @@ function date_interval_formatter_settings_summary($field, $instance, $view_mode) $display = $instance['display'][$view_mode]; $settings = $display['settings']; $formatter = $display['type']; - $summary[] = t('Display time ago, showing @interval units.', array('@interval' => $settings['interval'])); + $field = ($settings['use_end_date'] == 1) ? 'End' : 'Start'; + $summary[] = t('Display time ago, showing @interval units. Using @field Date', + array('@interval' => $settings['interval'], '@field' => $field)); return $summary; } @@ -191,7 +224,11 @@ function _date_field_instance_settings_form($field, $instance) { '#type' => 'select', '#title' => t('Default date'), '#default_value' => $settings['default_value'], - '#options' => array('blank' => t('No default value'), 'now' => t('Now'), 'strtotime' => t('Relative')), + '#options' => array( + 'blank' => t('No default value'), + 'now' => t('Now'), + 'strtotime' => t('Relative'), + ), '#weight' => 1, '#fieldset' => 'default_values', ); @@ -204,8 +241,11 @@ function _date_field_instance_settings_form($field, $instance) { '#default_value' => $settings['default_value_code'], '#states' => array( 'visible' => array( - ':input[name="instance[settings][default_value]"]' => array('value' => 'strtotime')), + ':input[name="instance[settings][default_value]"]' => array( + 'value' => 'strtotime', + ), ), + ), '#weight' => 1.1, '#fieldset' => 'default_values', ); @@ -213,7 +253,12 @@ function _date_field_instance_settings_form($field, $instance) { '#type' => !empty($field['settings']['todate']) ? 'select' : 'hidden', '#title' => t('Default end date'), '#default_value' => $settings['default_value2'], - '#options' => array('same' => t('Same as Default date'), 'blank' => t('No default value'), 'now' => t('Now'), 'strtotime' => t('Relative')), + '#options' => array( + 'same' => t('Same as Default date'), + 'blank' => t('No default value'), + 'now' => t('Now'), + 'strtotime' => t('Relative'), + ), '#weight' => 2, '#fieldset' => 'default_values', ); @@ -224,8 +269,11 @@ function _date_field_instance_settings_form($field, $instance) { '#default_value' => $settings['default_value_code2'], '#states' => array( 'visible' => array( - ':input[name="instance[settings][default_value2]"]' => array('value' => 'strtotime')), + ':input[name="instance[settings][default_value2]"]' => array( + 'value' => 'strtotime', + ), ), + ), '#weight' => 2.1, '#fieldset' => 'default_values', ); @@ -244,7 +292,7 @@ function _date_field_instance_settings_form($field, $instance) { /** * Form validation handler for _date_field_instance_settings_form(). */ -function date_field_instance_settings_form_validate(&$form, &$form_state) { +function _date_field_instance_settings_form_validate(&$form, &$form_state) { $settings = $form_state['values']['instance']['settings']; if ($settings['default_value'] == 'strtotime') { @@ -284,6 +332,7 @@ function _date_field_widget_settings_form($field, $instance) { $formats = drupal_map_assoc($formats); } $now = date_example_date(); + $options['site-wide'] = t('Short date format: @date', array('@date' => date_format_date($now, 'short'))); foreach ($formats as $f) { $options[$f] = date_format_date($now, 'custom', $f); } @@ -369,12 +418,19 @@ function _date_field_widget_settings_form($field, $instance) { '#weight' => 9, ); if (in_array($widget['type'], array('date_select'))) { - $options = array('above' => t('Above'), 'within' => t('Within'), 'none' => t('None')); - $description = t("The location of date part labels, like 'Year', 'Month', or 'Day' . 'Above' displays the label as titles above each date part. 'Within' inserts the label as the first option in the select list and in blank textfields. 'None' doesn't label any of the date parts. Theme functions like 'date_part_label_year' and 'date_part_label_month' control label text."); + $options = array( + 'above' => t('Above'), + 'within' => t('Within'), + 'none' => t('None'), + ); + $description = t("The location of date part labels, like 'Year', 'Month', or 'Day' . 'Above' displays the label as titles above each date part. 'Within' inserts the label as the first option in the select list and in blank textfields. 'None' doesn't visually label any of the date parts. Theme functions like 'date_part_label_year' and 'date_part_label_month' control label text."); } else { - $options = array('above' => t('Above'), 'none' => t('None')); - $description = t("The location of date part labels, like 'Year', 'Month', or 'Day' . 'Above' displays the label as titles above each date part. 'None' doesn't label any of the date parts. Theme functions like 'date_part_label_year' and 'date_part_label_month' control label text."); + $options = array( + 'above' => t('Above'), + 'none' => t('None'), + ); + $description = t("The location of date part labels, like 'Year', 'Month', or 'Day' . 'Above' displays the label as titles above each date part. 'None' doesn't visually label any of the date parts. Theme functions like 'date_part_label_year' and 'date_part_label_month' control label text."); } $form['advanced']['label_position'] = array( '#type' => 'radios', @@ -403,6 +459,13 @@ function _date_field_widget_settings_form($field, $instance) { } } + $form['advanced']['no_fieldset'] = array( + '#type' => 'checkbox', + '#title' => t('Render as a regular field'), + '#default_value' => !empty($settings['no_fieldset']), + '#description' => t('Whether to render this field as a regular field instead of a fieldset. The date field elements are wrapped in a fieldset by default, and may not display well without it.'), + ); + $context = array( 'field' => $field, 'instance' => $instance, @@ -415,7 +478,7 @@ function _date_field_widget_settings_form($field, $instance) { /** * Form validation handler for _date_field_widget_settings_form(). */ -function date_field_widget_settings_form_validate(&$form, &$form_state) { +function _date_field_widget_settings_form_validate(&$form, &$form_state) { // The widget settings are in the wrong place in the form because of #tree on // the top level. $settings = $form_state['values']['instance']['widget']['settings']; @@ -453,6 +516,9 @@ function _date_field_settings_form($field, $instance, $has_data) { $tz_handling = $settings['tz_handling']; $description = t('Select the date attributes to collect and store.'); + if ($has_data) { + $description .= ' ' . t('Changes to date attributes only effects new or updated content.'); + } $options = date_granularity_names(); $checkbox_year = array( '#type' => 'checkbox', @@ -467,9 +533,10 @@ function _date_field_settings_form($field, $instance, $has_data) { '#title' => t('Date attributes to collect'), '#default_value' => $granularity, '#options' => $options, - '#attributes' => array('class' => array('container-inline')), + '#attributes' => array( + 'class' => array('container-inline'), + ), '#description' => $description, - '#disabled' => $has_data, 'year' => $checkbox_year, ); @@ -499,7 +566,6 @@ function _date_field_settings_form($field, $instance, $has_data) { '#default_value' => $tz_handling, '#options' => date_timezone_handling_options(), '#description' => $description, - '#disabled' => $has_data, '#attached' => array( 'js' => array(drupal_get_path('module', 'date') . '/date_admin.js'), ), @@ -514,7 +580,7 @@ function _date_field_settings_form($field, $instance, $has_data) { $form['cache_enabled'] = array( '#type' => 'checkbox', '#title' => t('Cache dates'), - '#description' => t('Date objects can be created and cached as date fields are loaded rather than when they are displayed to improve performance.'), + '#description' => t('Date objects can be created and cached as date fields are loaded, rather than when they are displayed, to improve performance.'), '#default_value' => !empty($settings['cache_enabled']), '#weight' => 10, ); @@ -527,7 +593,9 @@ function _date_field_settings_form($field, $instance, $has_data) { '#weight' => 11, '#states' => array( 'visible' => array( - 'input[name="field[settings][cache_enabled]"]' => array('checked' => TRUE), + 'input[name="field[settings][cache_enabled]"]' => array( + 'checked' => TRUE, + ), ), ), ); @@ -545,7 +613,7 @@ function _date_field_settings_form($field, $instance, $has_data) { /** * Form validation handler for _date_field_settings_form(). */ -function date_field_settings_validate(&$form, &$form_state) { +function _date_field_settings_validate(&$form, &$form_state) { $field = &$form_state['values']['field']; if ($field['settings']['tz_handling'] == 'none') { @@ -599,7 +667,7 @@ function date_timezone_handling_options() { 'site' => t("Site's time zone"), 'date' => t("Date's time zone"), 'user' => t("User's time zone"), - 'utc' => 'UTC', + 'utc' => 'UTC', 'none' => t('No time zone conversion'), ); } diff --git a/sites/all/modules/contrib/fields/date/date_all_day/date_all_day.info b/sites/all/modules/contrib/fields/date/date_all_day/date_all_day.info index 8f8bf48..fcdc673 100644 --- a/sites/all/modules/contrib/fields/date/date_all_day/date_all_day.info +++ b/sites/all/modules/contrib/fields/date/date_all_day/date_all_day.info @@ -5,9 +5,9 @@ dependencies[] = date package = Date/Time core = 7.x -; Information added by drupal.org packaging script on 2012-08-13 -version = "7.x-2.6" +; Information added by Drupal.org packaging script on 2017-04-07 +version = "7.x-2.10" core = "7.x" project = "date" -datestamp = "1344850024" +datestamp = "1491562090" diff --git a/sites/all/modules/contrib/fields/date/date_all_day/date_all_day.module b/sites/all/modules/contrib/fields/date/date_all_day/date_all_day.module index 93fc61b..e79bc94 100644 --- a/sites/all/modules/contrib/fields/date/date_all_day/date_all_day.module +++ b/sites/all/modules/contrib/fields/date/date_all_day/date_all_day.module @@ -31,11 +31,11 @@ function date_all_day_theme() { 'format' => NULL, 'entity_type' => NULL, 'entity' => NULL, - 'view' => NULL - ) + 'view' => NULL, + ), ), 'date_all_day_label' => array( - 'variables' => array() + 'variables' => array(), ), ); @@ -91,14 +91,29 @@ function date_all_day_date_formatter_dates_alter(&$dates, $context) { /** * Adjust start/end date format to account for 'all day' . * - * @param array $field, the field definition for this date field. - * @param string $which, which value to return, 'date1' or 'date2' . - * @param object $date1, a date/time object for the 'start' date. - * @param object $date2, a date/time object for the 'end' date. - * @param string $format - * @param object $entity, the node this date comes from (may be incomplete, always contains nid). - * @param object $view, the view this node comes from, if applicable. - * @return formatted date. + * @params array $field + * The field definition for this date field. + * + * @params string $which + * Which value to return, 'date1' or 'date2'. + * + * @params object $date1 + * A date/time object for the 'start' date. + * + * @params object $date2 + * A date/time object for the 'end' date. + * + * @params string $format + * A date/time format + * + * @params object $entity + * The node this date comes from (may be incomplete, always contains nid). + * + * @params object $view + * The view this node comes from, if applicable. + * + * @return string + * Formatted date. */ function theme_date_all_day($vars) { $field = $vars['field']; @@ -135,23 +150,32 @@ function theme_date_all_day($vars) { } return trim(date_format_date($$which, 'custom', $format) . $suffix); - } /** * Theme the way an 'all day' label will look. */ function theme_date_all_day_label() { - return '(' . t('All day', array(), array('context' => 'datetime')) .')'; + return '(' . t('All day', array(), array('context' => 'datetime')) . ')'; } /** * Determine if a Start/End date combination qualify as 'All day'. * - * @param array $field, the field definition for this date field. - * @param object $date1, a date/time object for the 'Start' date. - * @param object $date2, a date/time object for the 'End' date. - * @return TRUE or FALSE. + * @param array $field + * The field definition for this date field. + * + * @param array $instance + * The field instance for this date field. + * + * @param object $date1 + * A date/time object for the 'Start' date. + * + * @param object $date2 + * A date/time object for the 'End' date. + * + * @return bool + * TRUE or FALSE. */ function date_all_day_field($field, $instance, $date1, $date2 = NULL) { if (empty($date1) || !is_object($date1)) { @@ -167,7 +191,6 @@ function date_all_day_field($field, $instance, $date1, $date2 = NULL) { $granularity = date_granularity_precision($field['settings']['granularity']); $increment = isset($instance['widget']['settings']['increment']) ? $instance['widget']['settings']['increment'] : 1; return date_is_all_day(date_format($date1, DATE_FORMAT_DATETIME), date_format($date2, DATE_FORMAT_DATETIME), $granularity, $increment); - } /** @@ -222,7 +245,8 @@ function date_all_day_date_combo_process_alter(&$element, &$form_state, $context function date_all_day_date_text_process_alter(&$element, &$form_state, $context) { $all_day_id = !empty($element['#date_all_day_id']) ? $element['#date_all_day_id'] : ''; if ($all_day_id != '') { - // All Day handling on text dates works only if the user leaves the time out of the input value. + // All Day handling on text dates works only + // if the user leaves the time out of the input value. // There is no element to hide or show. } } @@ -234,10 +258,11 @@ function date_all_day_date_text_process_alter(&$element, &$form_state, $context) */ function date_all_day_date_select_process_alter(&$element, &$form_state, $context) { - // Hide or show this element in reaction to the all_day status for this element. + // Hide or show this element in reaction + // to the all_day status for this element. $all_day_id = !empty($element['#date_all_day_id']) ? $element['#date_all_day_id'] : ''; if ($all_day_id != '') { - foreach(array('hour', 'minute', 'second', 'ampm') as $field) { + foreach (array('hour', 'minute', 'second', 'ampm') as $field) { if (array_key_exists($field, $element)) { $element[$field]['#states'] = array( 'visible' => array( @@ -255,7 +280,8 @@ function date_all_day_date_select_process_alter(&$element, &$form_state, $contex */ function date_all_day_date_popup_process_alter(&$element, &$form_state, $context) { - // Hide or show this element in reaction to the all_day status for this element. + // Hide or show this element in reaction to + // the all_day status for this element. $all_day_id = !empty($element['#date_all_day_id']) ? $element['#date_all_day_id'] : ''; if ($all_day_id != '' && array_key_exists('time', $element)) { $element['time']['#states'] = array( @@ -272,7 +298,8 @@ function date_all_day_date_popup_process_alter(&$element, &$form_state, $context * of the date_select validation gets fired. */ function date_all_day_date_text_pre_validate_alter(&$element, &$form_state, &$input) { - // Let Date module massage the format for all day values so they will pass validation. + // Let Date module massage the format for all day + // values so they will pass validation. // The All day flag, if used, actually exists on the parent element. date_all_day_value($element, $form_state); } @@ -284,7 +311,8 @@ function date_all_day_date_text_pre_validate_alter(&$element, &$form_state, &$in * of the date_select validation gets fired. */ function date_all_day_date_select_pre_validate_alter(&$element, &$form_state, &$input) { - // Let Date module massage the format for all day values so they will pass validation. + // Let Date module massage the format for all + // day values so they will pass validation. // The All day flag, if used, actually exists on the parent element. date_all_day_value($element, $form_state); } @@ -296,13 +324,16 @@ function date_all_day_date_select_pre_validate_alter(&$element, &$form_state, &$ * of the date_popup validation gets fired. */ function date_all_day_date_popup_pre_validate_alter(&$element, &$form_state, &$input) { - // Let Date module massage the format for all day values so they will pass validation. + // Let Date module massage the format for all + // day values so they will pass validation. // The All day flag, if used, actually exists on the parent element. date_all_day_value($element, $form_state); } /** - * A helper function to check if the all day flag is set on the parent of an + * A helper function date_all_day_value(). + * + * To check if the all day flag is set on the parent of an * element, and adjust the date_format accordingly so the missing time will * not cause validation errors. */ @@ -332,7 +363,8 @@ function date_all_day_date_combo_pre_validate_alter(&$element, &$form_state, $co $field = $context['field']; // If we have an all day flag on this date and the time is empty, - // change the format to match the input value so we don't get validation errors. + // change the format to match the input value + // so we don't get validation errors. $element['#date_is_all_day'] = TRUE; $element['value']['#date_format'] = date_part_format('date', $element['value']['#date_format']); if (!empty($field['settings']['todate'])) { @@ -344,29 +376,29 @@ function date_all_day_date_combo_pre_validate_alter(&$element, &$form_state, $co /** * Implements hook_date_combo_validate_date_start_alter(). * - * This hook lets us alter the local date objects created by the date_combo validation - * before they are converted back to the database timezone and stored. + * This hook lets us alter the local date objects + * created by the date_combo validation before they are + * converted back to the database timezone and stored. */ function date_all_day_date_combo_validate_date_start_alter(&$date, &$form_state, $context) { - - // If this is an 'All day' value, set the time to midnight. - if (!empty($context['element']['#date_is_all_day'])) { - $date->setTime(0, 0, 0); - } + // If this is an 'All day' value, set the time to midnight. + if (!empty($context['element']['#date_is_all_day'])) { + $date->setTime(0, 0, 0); + } } /** * Implements hook_date_combo_validate_date_end_alter(). * - * This hook lets us alter the local date objects created by the date_combo validation - * before they are converted back to the database timezone and stored. + * This hook lets us alter the local date objects + * created by the date_combo validation before + * they are converted back to the database timezone and stored. */ function date_all_day_date_combo_validate_date_end_alter(&$date, &$form_state, $context) { - - // If this is an 'All day' value, set the time to midnight. - if (!empty($context['element']['#date_is_all_day'])) { - $date->setTime(0, 0, 0); - } + // If this is an 'All day' value, set the time to midnight. + if (!empty($context['element']['#date_is_all_day'])) { + $date->setTime(0, 0, 0); + } } /** diff --git a/sites/all/modules/contrib/fields/date/date_api/date.css b/sites/all/modules/contrib/fields/date/date_api/date.css index 9b72ecd..e47a2ae 100644 --- a/sites/all/modules/contrib/fields/date/date_api/date.css +++ b/sites/all/modules/contrib/fields/date/date_api/date.css @@ -15,9 +15,11 @@ .container-inline-date > .form-item { display: inline-block; margin-right: 0.5em; /* LTR */ - margin-bottom: 10px; vertical-align: top; } +fieldset.date-combo .container-inline-date > .form-item { + margin-bottom: 10px; +} .container-inline-date .form-item .form-item { float: left; /* LTR */ } @@ -52,9 +54,11 @@ /* The exposed Views form doesn't need some of these styles */ .container-inline-date .date-padding { - padding: 10px; float: left; } +fieldset.date-combo .container-inline-date .date-padding { + padding: 10px; +} .views-exposed-form .container-inline-date .date-padding { padding: 0; } @@ -116,7 +120,7 @@ span.date-display-end { } /* Add space between the date and time portions of the date_select widget. */ -.form-type-date-select .form-type-select[class$=hour] { +.form-type-date-select .form-type-select[class*=hour] { margin-left: .75em; /* LTR */ } @@ -173,6 +177,10 @@ div.date-calendar-day span.year { padding: 2px; } +.date-form-element-content-multiline { + padding: 10px; + border: 1px solid #CCC; +} /* Admin styling */ .form-item.form-item-instance-widget-settings-input-format-custom, .form-item.form-item-field-settings-enddate-required { diff --git a/sites/all/modules/contrib/fields/date/date_api/date_api.admin.inc b/sites/all/modules/contrib/fields/date/date_api/date_api.admin.inc index 966b1a4..3c706ae 100644 --- a/sites/all/modules/contrib/fields/date/date_api/date_api.admin.inc +++ b/sites/all/modules/contrib/fields/date/date_api/date_api.admin.inc @@ -10,112 +10,112 @@ */ function _date_timezone_replacement($old) { $replace = array( - 'Brazil/Acre' => 'America/Rio_Branco', - 'Brazil/DeNoronha' => 'America/Noronha', - 'Brazil/East' => 'America/Recife', - 'Brazil/West' => 'America/Manaus', - 'Canada/Atlantic' => 'America/Halifax', - 'Canada/Central' => 'America/Winnipeg', - 'Canada/East-Saskatchewan' => 'America/Regina', - 'Canada/Eastern' => 'America/Toronto', - 'Canada/Mountain' => 'America/Edmonton', - 'Canada/Newfoundland' => 'America/St_Johns', - 'Canada/Pacific' => 'America/Vancouver', - 'Canada/Saskatchewan' => 'America/Regina', - 'Canada/Yukon' => 'America/Whitehorse', - 'CET' => 'Europe/Berlin', - 'Chile/Continental' => 'America/Santiago', - 'Chile/EasterIsland' => 'Pacific/Easter', - 'CST6CDT' => 'America/Chicago', - 'Cuba' => 'America/Havana', - 'EET' => 'Europe/Bucharest', - 'Egypt' => 'Africa/Cairo', - 'Eire' => 'Europe/Belfast', - 'EST' => 'America/New_York', - 'EST5EDT' => 'America/New_York', - 'GB' => 'Europe/London', - 'GB-Eire' => 'Europe/Belfast', - 'Etc/GMT' => 'UTC', - 'Etc/GMT+0' => 'UTC', - 'Etc/GMT+1' => 'UTC', - 'Etc/GMT+10' => 'UTC', - 'Etc/GMT+11' => 'UTC', - 'Etc/GMT+12' => 'UTC', - 'Etc/GMT+2' => 'UTC', - 'Etc/GMT+3' => 'UTC', - 'Etc/GMT+4' => 'UTC', - 'Etc/GMT+5' => 'UTC', - 'Etc/GMT+6' => 'UTC', - 'Etc/GMT+7' => 'UTC', - 'Etc/GMT+8' => 'UTC', - 'Etc/GMT+9' => 'UTC', - 'Etc/GMT-0' => 'UTC', - 'Etc/GMT-1' => 'UTC', - 'Etc/GMT-10' => 'UTC', - 'Etc/GMT-11' => 'UTC', - 'Etc/GMT-12' => 'UTC', - 'Etc/GMT-13' => 'UTC', - 'Etc/GMT-14' => 'UTC', - 'Etc/GMT-2' => 'UTC', - 'Etc/GMT-3' => 'UTC', - 'Etc/GMT-4' => 'UTC', - 'Etc/GMT-5' => 'UTC', - 'Etc/GMT-6' => 'UTC', - 'Etc/GMT-7' => 'UTC', - 'Etc/GMT-8' => 'UTC', - 'Etc/GMT-9' => 'UTC', - 'Etc/GMT0' => 'UTC', - 'Etc/Greenwich' => 'UTC', - 'Etc/UCT' => 'UTC', - 'Etc/Universal' => 'UTC', - 'Etc/UTC' => 'UTC', - 'Etc/Zulu' => 'UTC', - 'Factory' => 'UTC', - 'GMT' => 'UTC', - 'GMT+0' => 'UTC', - 'GMT-0' => 'UTC', - 'GMT0' => 'UTC', - 'Hongkong' => 'Asia/Hong_Kong', - 'HST' => 'Pacific/Honolulu', - 'Iceland' => 'Atlantic/Reykjavik', - 'Iran' => 'Asia/Tehran', - 'Israel' => 'Asia/Tel_Aviv', - 'Jamaica' => 'America/Jamaica', - 'Japan' => 'Asia/Tokyo', - 'Kwajalein' => 'Pacific/Kwajalein', - 'Libya' => 'Africa/Tunis', - 'MET' => 'Europe/Budapest', - 'Mexico/BajaNorte' => 'America/Tijuana', - 'Mexico/BajaSur' => 'America/Mazatlan', - 'Mexico/General' => 'America/Mexico_City', - 'MST' => 'America/Boise', - 'MST7MDT' => 'America/Boise', - 'Navajo' => 'America/Phoenix', - 'NZ' => 'Pacific/Auckland', - 'NZ-CHAT' => 'Pacific/Chatham', - 'Poland' => 'Europe/Warsaw', - 'Portugal' => 'Europe/Lisbon', - 'PRC' => 'Asia/Chongqing', - 'PST8PDT' => 'America/Los_Angeles', - 'ROC' => 'Asia/Taipei', - 'ROK' => 'Asia/Seoul', - 'Singapore' => 'Asia/Singapore', - 'Turkey' => 'Europe/Istanbul', - 'US/Alaska' => 'America/Anchorage', - 'US/Aleutian' => 'America/Adak', - 'US/Arizona' => 'America/Phoenix', - 'US/Central' => 'America/Chicago', - 'US/East-Indiana' => 'America/Indianapolis', - 'US/Eastern' => 'America/New_York', - 'US/Hawaii' => 'Pacific/Honolulu', - 'US/Indiana-Starke' => 'America/Indiana/Knox', - 'US/Michigan' => 'America/Detroit', - 'US/Mountain' => 'America/Boise', - 'US/Pacific' => 'America/Los_Angeles', - 'US/Pacific-New' => 'America/Los_Angeles', - 'US/Samoa' => 'Pacific/Samoa', - 'W-SU' => 'Europe/Moscow', - 'WET' => 'Europe/Paris', + 'Brazil/Acre' => 'America/Rio_Branco', + 'Brazil/DeNoronha' => 'America/Noronha', + 'Brazil/East' => 'America/Recife', + 'Brazil/West' => 'America/Manaus', + 'Canada/Atlantic' => 'America/Halifax', + 'Canada/Central' => 'America/Winnipeg', + 'Canada/East-Saskatchewan' => 'America/Regina', + 'Canada/Eastern' => 'America/Toronto', + 'Canada/Mountain' => 'America/Edmonton', + 'Canada/Newfoundland' => 'America/St_Johns', + 'Canada/Pacific' => 'America/Vancouver', + 'Canada/Saskatchewan' => 'America/Regina', + 'Canada/Yukon' => 'America/Whitehorse', + 'CET' => 'Europe/Berlin', + 'Chile/Continental' => 'America/Santiago', + 'Chile/EasterIsland' => 'Pacific/Easter', + 'CST6CDT' => 'America/Chicago', + 'Cuba' => 'America/Havana', + 'EET' => 'Europe/Bucharest', + 'Egypt' => 'Africa/Cairo', + 'Eire' => 'Europe/Belfast', + 'EST' => 'America/New_York', + 'EST5EDT' => 'America/New_York', + 'GB' => 'Europe/London', + 'GB-Eire' => 'Europe/Belfast', + 'Etc/GMT' => 'UTC', + 'Etc/GMT+0' => 'UTC', + 'Etc/GMT+1' => 'UTC', + 'Etc/GMT+10' => 'UTC', + 'Etc/GMT+11' => 'UTC', + 'Etc/GMT+12' => 'UTC', + 'Etc/GMT+2' => 'UTC', + 'Etc/GMT+3' => 'UTC', + 'Etc/GMT+4' => 'UTC', + 'Etc/GMT+5' => 'UTC', + 'Etc/GMT+6' => 'UTC', + 'Etc/GMT+7' => 'UTC', + 'Etc/GMT+8' => 'UTC', + 'Etc/GMT+9' => 'UTC', + 'Etc/GMT-0' => 'UTC', + 'Etc/GMT-1' => 'UTC', + 'Etc/GMT-10' => 'UTC', + 'Etc/GMT-11' => 'UTC', + 'Etc/GMT-12' => 'UTC', + 'Etc/GMT-13' => 'UTC', + 'Etc/GMT-14' => 'UTC', + 'Etc/GMT-2' => 'UTC', + 'Etc/GMT-3' => 'UTC', + 'Etc/GMT-4' => 'UTC', + 'Etc/GMT-5' => 'UTC', + 'Etc/GMT-6' => 'UTC', + 'Etc/GMT-7' => 'UTC', + 'Etc/GMT-8' => 'UTC', + 'Etc/GMT-9' => 'UTC', + 'Etc/GMT0' => 'UTC', + 'Etc/Greenwich' => 'UTC', + 'Etc/UCT' => 'UTC', + 'Etc/Universal' => 'UTC', + 'Etc/UTC' => 'UTC', + 'Etc/Zulu' => 'UTC', + 'Factory' => 'UTC', + 'GMT' => 'UTC', + 'GMT+0' => 'UTC', + 'GMT-0' => 'UTC', + 'GMT0' => 'UTC', + 'Hongkong' => 'Asia/Hong_Kong', + 'HST' => 'Pacific/Honolulu', + 'Iceland' => 'Atlantic/Reykjavik', + 'Iran' => 'Asia/Tehran', + 'Israel' => 'Asia/Tel_Aviv', + 'Jamaica' => 'America/Jamaica', + 'Japan' => 'Asia/Tokyo', + 'Kwajalein' => 'Pacific/Kwajalein', + 'Libya' => 'Africa/Tunis', + 'MET' => 'Europe/Budapest', + 'Mexico/BajaNorte' => 'America/Tijuana', + 'Mexico/BajaSur' => 'America/Mazatlan', + 'Mexico/General' => 'America/Mexico_City', + 'MST' => 'America/Boise', + 'MST7MDT' => 'America/Boise', + 'Navajo' => 'America/Phoenix', + 'NZ' => 'Pacific/Auckland', + 'NZ-CHAT' => 'Pacific/Chatham', + 'Poland' => 'Europe/Warsaw', + 'Portugal' => 'Europe/Lisbon', + 'PRC' => 'Asia/Chongqing', + 'PST8PDT' => 'America/Los_Angeles', + 'ROC' => 'Asia/Taipei', + 'ROK' => 'Asia/Seoul', + 'Singapore' => 'Asia/Singapore', + 'Turkey' => 'Europe/Istanbul', + 'US/Alaska' => 'America/Anchorage', + 'US/Aleutian' => 'America/Adak', + 'US/Arizona' => 'America/Phoenix', + 'US/Central' => 'America/Chicago', + 'US/East-Indiana' => 'America/Indianapolis', + 'US/Eastern' => 'America/New_York', + 'US/Hawaii' => 'Pacific/Honolulu', + 'US/Indiana-Starke' => 'America/Indiana/Knox', + 'US/Michigan' => 'America/Detroit', + 'US/Mountain' => 'America/Boise', + 'US/Pacific' => 'America/Los_Angeles', + 'US/Pacific-New' => 'America/Los_Angeles', + 'US/Samoa' => 'Pacific/Samoa', + 'W-SU' => 'Europe/Moscow', + 'WET' => 'Europe/Paris', ); if (array_key_exists($old, $replace)) { return $replace[$old]; diff --git a/sites/all/modules/contrib/fields/date/date_api/date_api.info b/sites/all/modules/contrib/fields/date/date_api/date_api.info index ee1a384..c537ca0 100644 --- a/sites/all/modules/contrib/fields/date/date_api/date_api.info +++ b/sites/all/modules/contrib/fields/date/date_api/date_api.info @@ -9,9 +9,9 @@ stylesheets[all][] = date.css files[] = date_api.module files[] = date_api_sql.inc -; Information added by drupal.org packaging script on 2012-08-13 -version = "7.x-2.6" +; Information added by Drupal.org packaging script on 2017-04-07 +version = "7.x-2.10" core = "7.x" project = "date" -datestamp = "1344850024" +datestamp = "1491562090" diff --git a/sites/all/modules/contrib/fields/date/date_api/date_api.install b/sites/all/modules/contrib/fields/date/date_api/date_api.install index ce5b746..d5a68bb 100644 --- a/sites/all/modules/contrib/fields/date/date_api/date_api.install +++ b/sites/all/modules/contrib/fields/date/date_api/date_api.install @@ -96,7 +96,7 @@ function date_api_uninstall() { 'date_php_min_year', 'date_db_tz_support', 'date_api_use_iso8601', - ); + ); foreach ($variables as $variable) { variable_del($variable); } @@ -118,8 +118,9 @@ function date_api_update_last_removed() { } /** - * Move old date format data to new date format tables, and delete the old - * tables. Insert only values that don't already exist in the new tables, in + * Move old date format to new date format tables,and delete the old tables. + * + * Insert only values that don't already exist in the new tables, in * case new version of those custom values have already been created. */ function date_api_update_7000() { diff --git a/sites/all/modules/contrib/fields/date/date_api/date_api.module b/sites/all/modules/contrib/fields/date/date_api/date_api.module index d85a8a5..160bc9f 100644 --- a/sites/all/modules/contrib/fields/date/date_api/date_api.module +++ b/sites/all/modules/contrib/fields/date/date_api/date_api.module @@ -59,16 +59,16 @@ function date_help($path, $arg) { } if (module_exists('date_tools')) { - $output .= '

    Date Tools

    ' . t('Dates and calendars can be complicated to set up. The !date_wizard makes it easy to create a simple date content type and with a date field. ', array('!date_wizard' => l(t('Date wizard'), 'admin/config/date/tools/date_wizard'))); + $output .= '

    Date Tools

    ' . t('Dates and calendars can be complicated to set up. The !date_wizard makes it easy to create a simple date content type and with a date field.', array('!date_wizard' => l(t('Date wizard'), 'admin/config/date/tools/date_wizard'))); } else { - $output .= '

    Date Tools

    ' . t('Dates and calendars can be complicated to set up. If you enable the Date Tools module, it provides a Date Wizard that makes it easy to create a simple date content type with a date field. '); + $output .= '

    Date Tools

    ' . t('Dates and calendars can be complicated to set up. If you enable the Date Tools module, it provides a Date Wizard that makes it easy to create a simple date content type with a date field.'); } $output .= '

    More Information

    ' . t('Complete documentation for the Date and Date API modules is available at http://drupal.org/node/92460.', array('@link' => 'http://drupal.org/node/262062')) . '

    '; return $output; - break; + } } @@ -101,7 +101,7 @@ function date_api_status() { $value = variable_get('date_format_medium'); if (isset($value)) { $now = date_now(); - $success_messages[] = $t('The medium date format type has been set to to @value. You may find it helpful to add new format types like Date, Time, Month, or Year, with appropriate formats, at Date and time settings.', array('@value' => $now->format($value), '@regional_date_time' => url('admin/config/regional/date-time'))); + $success_messages[] = $t('The medium date format type has been set to @value. You may find it helpful to add new format types like Date, Time, Month, or Year, with appropriate formats, at Date and time settings.', array('@value' => $now->format($value), '@regional_date_time' => url('admin/config/regional/date-time'))); } else { $error_messages[] = $t('The Date API requires that you set up the system date formats to function correctly.', array('@regional_date_time' => url('admin/config/regional/date-time'))); @@ -143,7 +143,15 @@ function date_api_menu() { class DateObject extends DateTime { public $granularity = array(); public $errors = array(); - protected static $allgranularity = array('year', 'month', 'day', 'hour', 'minute', 'second', 'timezone'); + protected static $allgranularity = array( + 'year', + 'month', + 'day', + 'hour', + 'minute', + 'second', + 'timezone' + ); private $serializedTime; private $serializedTimezone; @@ -276,18 +284,17 @@ class DateObject extends DateTime { $this->setGranularityFromTime($time, $tz); } } - // If this tz was given as just an offset or the timezone - // was invalid, we need to do some tweaking. + + // If we haven't got a valid timezone name yet, we need to set one or + // we will get undefined index errors. + // This can happen if $time had an offset or no timezone. if (!$this->getTimezone() || !preg_match('/[a-zA-Z]/', $this->getTimezone()->getName())) { - // If the timezone name is an offset and the original - // $tz has a name, use it. This happens if you pass in - // a date string with an offset along with a specific timezone name. - if (!preg_match('/[a-zA-Z]/', $this->getTimezone()->getName()) && preg_match('/[a-zA-Z]/', $tz->getName())) { + // If the original $tz has a name, use it. + if (preg_match('/[a-zA-Z]/', $tz->getName())) { $this->setTimezone($tz); } - // If we get this far, we have no information about the timezone name, - // but we will get undefined index errors without any name. + // We have no information about the timezone so must fallback to a default. else { $this->setTimezone(new DateTimeZone("UTC")); $this->errors['timezone'] = t('No valid timezone name was provided.'); @@ -403,7 +410,7 @@ class DateObject extends DateTime { * A single date part. */ public function removeGranularity($g) { - if ($key = array_search($g, $this->granularity)) { + if (($key = array_search($g, $this->granularity)) !== FALSE) { unset($this->granularity[$key]); } } @@ -459,8 +466,39 @@ class DateObject extends DateTime { $true = $this->hasGranularity() && (!$granularity || $flexible || $this->hasGranularity($granularity)); if (!$true && $granularity) { foreach ((array) $granularity as $part) { - if (!$this->hasGranularity($part)) { - $this->errors[$part] = t("The @part is missing.", array('@part' => $part)); + if (!$this->hasGranularity($part) && in_array($part, array( + 'second', + 'minute', + 'hour', + 'day', + 'month', + 'year') + )) { + switch ($part) { + case 'second': + $this->errors[$part] = t('The second is missing.'); + break; + + case 'minute': + $this->errors[$part] = t('The minute is missing.'); + break; + + case 'hour': + $this->errors[$part] = t('The hour is missing.'); + break; + + case 'day': + $this->errors[$part] = t('The day is missing.'); + break; + + case 'month': + $this->errors[$part] = t('The month is missing.'); + break; + + case 'year': + $this->errors[$part] = t('The year is missing.'); + break; + } } } } @@ -519,7 +557,14 @@ class DateObject extends DateTime { $temp = date_parse($time); // Special case for 'now'. if ($time == 'now') { - $this->granularity = array('year', 'month', 'day', 'hour', 'minute', 'second'); + $this->granularity = array( + 'year', + 'month', + 'day', + 'hour', + 'minute', + 'second', + ); } else { // This PHP date_parse() method currently doesn't have resolution down to @@ -574,7 +619,7 @@ class DateObject extends DateTime { $regex2 = preg_replace($patterns, $repl2, $format_regexp, 1); $regex2 = str_replace('A', '(AM|PM)', $regex2); $regex2 = str_replace('a', '(am|pm)', $regex2); - preg_match('`^' . $regex2 . '$`', $date, $values); + preg_match('`^' . $regex2 . '$`u', $date, $values); array_shift($values); // If we did not find all the values for the patterns in the format, abort. if (count($letters) != count($values)) { @@ -582,7 +627,14 @@ class DateObject extends DateTime { return FALSE; } $this->granularity = array(); - $final_date = array('hour' => 0, 'minute' => 0, 'second' => 0, 'month' => 1, 'day' => 1, 'year' => 0); + $final_date = array( + 'hour' => 0, + 'minute' => 0, + 'second' => 0, + 'month' => 1, + 'day' => 1, + 'year' => 0, + ); foreach ($letters as $i => $letter) { $value = $values[$i]; switch ($letter) { @@ -591,21 +643,25 @@ class DateObject extends DateTime { $final_date['day'] = intval($value); $this->addGranularity('day'); break; + case 'n': case 'm': $final_date['month'] = intval($value); $this->addGranularity('month'); break; + case 'F': $array_month_long = array_flip(date_month_names()); $final_date['month'] = array_key_exists($value, $array_month_long) ? $array_month_long[$value] : -1; $this->addGranularity('month'); break; + case 'M': $array_month = array_flip(date_month_names_abbr()); $final_date['month'] = array_key_exists($value, $array_month) ? $array_month[$value] : -1; $this->addGranularity('month'); break; + case 'Y': $final_date['year'] = $value; $this->addGranularity('year'); @@ -613,16 +669,19 @@ class DateObject extends DateTime { $this->errors['year'] = t('The year is invalid. Please check that entry includes four digits.'); } break; + case 'y': $year = $value; // If no century, we add the current one ("06" => "2006"). $final_date['year'] = str_pad($year, 4, substr(date("Y"), 0, 2), STR_PAD_LEFT); $this->addGranularity('year'); break; + case 'a': case 'A': $ampm = strtolower($value); break; + case 'g': case 'h': case 'G': @@ -630,14 +689,17 @@ class DateObject extends DateTime { $final_date['hour'] = intval($value); $this->addGranularity('hour'); break; + case 'i': $final_date['minute'] = intval($value); $this->addGranularity('minute'); break; + case 's': $final_date['second'] = intval($value); $this->addGranularity('second'); break; + case 'U': parent::__construct($value, $tz ? $tz : new DateTimeZone("UTC")); $this->addGranularity('year'); @@ -647,7 +709,7 @@ class DateObject extends DateTime { $this->addGranularity('minute'); $this->addGranularity('second'); return $this; - break; + } } if (isset($ampm) && $ampm == 'pm' && $final_date['hour'] < 12) { @@ -740,10 +802,24 @@ class DateObject extends DateTime { // date or we will get date slippage, i.e. a value of 2011-00-00 will get // interpreted as November of 2010 by PHP. if ($full) { - $arr += array('year' => 0, 'month' => 1, 'day' => 1, 'hour' => 0, 'minute' => 0, 'second' => 0); + $arr += array( + 'year' => 0, + 'month' => 1, + 'day' => 1, + 'hour' => 0, + 'minute' => 0, + 'second' => 0, + ); } else { - $arr += array('year' => '', 'month' => '', 'day' => '', 'hour' => '', 'minute' => '', 'second' => ''); + $arr += array( + 'year' => '', + 'month' => '', + 'day' => '', + 'hour' => '', + 'minute' => '', + 'second' => '', + ); } $datetime = ''; if ($arr['year'] !== '') { @@ -821,28 +897,27 @@ class DateObject extends DateTime { case 'year': $fallback = $now->format('Y'); return !is_int($value) || empty($value) || $value < variable_get('date_min_year', 1) || $value > variable_get('date_max_year', 4000) ? $fallback : $value; - break; + case 'month': $fallback = $default == 'first' ? 1 : $now->format('n'); return !is_int($value) || empty($value) || $value <= 0 || $value > 12 ? $fallback : $value; - break; + case 'day': $fallback = $default == 'first' ? 1 : $now->format('j'); $max_day = isset($year) && isset($month) ? date_days_in_month($year, $month) : 31; return !is_int($value) || empty($value) || $value <= 0 || $value > $max_day ? $fallback : $value; - break; + case 'hour': $fallback = $default == 'first' ? 0 : $now->format('G'); return !is_int($value) || $value < 0 || $value > 23 ? $fallback : $value; - break; + case 'minute': $fallback = $default == 'first' ? 0 : $now->format('i'); return !is_int($value) || $value < 0 || $value > 59 ? $fallback : $value; - break; + case 'second': $fallback = $default == 'first' ? 0 : $now->format('s'); return !is_int($value) || $value < 0 || $value > 59 ? $fallback : $value; - break; } } @@ -880,18 +955,23 @@ class DateObject extends DateTime { case 'year': $errors['year'] = t('The year is invalid.'); break; + case 'month': $errors['month'] = t('The month is invalid.'); break; + case 'day': $errors['day'] = t('The day is invalid.'); break; + case 'hour': $errors['hour'] = t('The hour is invalid.'); break; + case 'minute': $errors['minute'] = t('The minute is invalid.'); break; + case 'second': $errors['second'] = t('The second is invalid.'); break; @@ -911,7 +991,7 @@ class DateObject extends DateTime { * The stop date. * @param string $measure * (optional) A granularity date part. Defaults to 'seconds'. - * @param boolean $absolute + * @param bool $absolute * (optional) Indicate whether the absolute value of the difference should * be returned or if the sign should be retained. Defaults to TRUE. */ @@ -937,10 +1017,13 @@ class DateObject extends DateTime { // The easy cases first. case 'seconds': return $diff; + case 'minutes': return $diff / 60; + case 'hours': return $diff / 3600; + case 'years': return $year_diff; @@ -951,6 +1034,11 @@ class DateObject extends DateTime { if ($year_diff == 0) { return intval($item2 - $item1); } + elseif ($year_diff < 0) { + $item_diff = 0 - $item1; + $item_diff -= intval((abs($year_diff) - 1) * 12); + return $item_diff - (12 - $item2); + } else { $item_diff = 12 - $item1; $item_diff += intval(($year_diff - 1) * 12); @@ -965,6 +1053,14 @@ class DateObject extends DateTime { if ($year_diff == 0) { return intval($item2 - $item1); } + elseif ($year_diff < 0) { + $item_diff = 0 - $item1; + for ($i = 1; $i < abs($year_diff); $i++) { + date_modify($date1, '-1 year'); + $item_diff -= date_days_in_year($date1); + } + return $item_diff - (date_days_in_year($date2) - $item2); + } else { $item_diff = date_days_in_year($date1) - $item1; for ($i = 1; $i < $year_diff; $i++) { @@ -978,9 +1074,12 @@ class DateObject extends DateTime { case 'weeks': $week_diff = date_format($date2, 'W') - date_format($date1, 'W'); $year_diff = date_format($date2, 'o') - date_format($date1, 'o'); - for ($i = 1; $i <= $year_diff; $i++) { - date_modify($date1, '+1 year'); - $week_diff += date_iso_weeks_in_year($date1); + + $sign = ($year_diff < 0) ? -1 : 1; + + for ($i = 1; $i <= abs($year_diff); $i++) { + date_modify($date1, (($sign > 0) ? '+' : '-') . '1 year'); + $week_diff += (date_iso_weeks_in_year($date1) * $sign); } return $week_diff; } @@ -1026,10 +1125,13 @@ function date_type_format($type) { switch ($type) { case DATE_ISO: return DATE_FORMAT_ISO; + case DATE_UNIX: return DATE_FORMAT_UNIX; + case DATE_DATETIME: return DATE_FORMAT_DATETIME; + case DATE_ICAL: return DATE_FORMAT_ICAL; } @@ -1085,7 +1187,7 @@ function date_month_names($required = FALSE) { } /** - * Constructs a translated array of month name abbreviations + * Constructs a translated array of month name abbreviations. * * @param bool $required * (optional) If FALSE, the returned array will include a blank value. @@ -1177,9 +1279,11 @@ function date_week_days_abbr($required = FALSE, $refresh = TRUE, $length = 3) { case 1: $context = 'day_abbr1'; break; + case 2: $context = 'day_abbr2'; break; + default: $context = ''; break; @@ -1214,10 +1318,10 @@ function date_week_days_ordered($weekdays) { /** * Constructs an array of years. * - * @param int $min - * The minimum year in the array. - * @param int $max - * The maximum year in the array. + * @param int $start + * The start year in the array. + * @param int $end + * The end year in the array. * @param bool $required * (optional) If FALSE, the returned array will include a blank value. * Defaults to FALSE. @@ -1225,16 +1329,16 @@ function date_week_days_ordered($weekdays) { * @return array * An array of years in the selected range. */ -function date_years($min = 0, $max = 0, $required = FALSE) { +function date_years($start = 0, $end = 0, $required = FALSE) { // Ensure $min and $max are valid values. - if (empty($min)) { - $min = intval(date('Y', REQUEST_TIME) - 3); + if (empty($start)) { + $start = intval(date('Y', REQUEST_TIME) - 3); } - if (empty($max)) { - $max = intval(date('Y', REQUEST_TIME) + 3); + if (empty($end)) { + $end = intval(date('Y', REQUEST_TIME) + 3); } $none = array(0 => ''); - return !$required ? $none + drupal_map_assoc(range($min, $max)) : drupal_map_assoc(range($min, $max)); + return !$required ? $none + drupal_map_assoc(range($start, $end)) : drupal_map_assoc(range($start, $end)); } /** @@ -1440,7 +1544,14 @@ function date_granularity_names() { * An array of date parts. */ function date_granularity_sorted($granularity) { - return array_intersect(array('year', 'month', 'day', 'hour', 'minute', 'second'), $granularity); + return array_intersect(array( + 'year', + 'month', + 'day', + 'hour', + 'minute', + 'second', + ), $granularity); } /** @@ -1458,14 +1569,19 @@ function date_granularity_array_from_precision($precision) { switch ($precision) { case 'year': return array_slice($granularity_array, -6, 1); + case 'month': return array_slice($granularity_array, -6, 2); + case 'day': return array_slice($granularity_array, -6, 3); + case 'hour': return array_slice($granularity_array, -6, 4); + case 'minute': return array_slice($granularity_array, -6, 5); + default: return $granularity_array; } @@ -1499,14 +1615,19 @@ function date_granularity_format($granularity) { switch ($granularity) { case 'year': return substr($format, 0, 1); + case 'month': return substr($format, 0, 3); + case 'day': return substr($format, 0, 5); + case 'hour'; return substr($format, 0, 7); + case 'minute': return substr($format, 0, 9); + default: return $format; } @@ -1623,40 +1744,51 @@ function date_format_date($date, $type = 'medium', $format = '', $langcode = NUL case 'l': $datestring .= t($date->format('l'), array(), array('context' => '', 'langcode' => $langcode)); break; + case 'D': $datestring .= t($date->format('D'), array(), array('context' => '', 'langcode' => $langcode)); break; + case 'F': $datestring .= t($date->format('F'), array(), array('context' => 'Long month name', 'langcode' => $langcode)); break; + case 'M': $datestring .= t($date->format('M'), array(), array('langcode' => $langcode)); break; + case 'A': case 'a': $datestring .= t($date->format($c), array(), array('context' => 'ampm', 'langcode' => $langcode)); break; + // The timezone name translations can use t(). case 'e': case 'T': $datestring .= t($date->format($c)); break; + // Remaining date parts need no translation. case 'O': $datestring .= sprintf('%s%02d%02d', (date_offset_get($date) < 0 ? '-' : '+'), abs(date_offset_get($date) / 3600), abs(date_offset_get($date) % 3600) / 60); break; + case 'P': $datestring .= sprintf('%s%02d:%02d', (date_offset_get($date) < 0 ? '-' : '+'), abs(date_offset_get($date) / 3600), abs(date_offset_get($date) % 3600) / 60); break; + case 'Z': $datestring .= date_offset_get($date); break; + case '\\': $datestring .= $format[++$i]; break; + case 'r': - $datestring .= date_format_date($date, 'custom', 'D, d M Y H:i:s O', $langcode); + $datestring .= date_format_date($date, 'custom', 'D, d M Y H:i:s O', 'en'); break; + default: if (strpos('BdcgGhHiIjLmnNosStTuUwWYyz', $c) !== FALSE) { $datestring .= $date->format($c); @@ -1701,15 +1833,38 @@ function date_format_interval($date, $granularity = 2, $display_ago = TRUE) { /** * A date object for the current time. * - * @param object $timezone - * (optional) Optionally force time to a specific timezone, defaults to user - * timezone, if set, otherwise site timezone. Defaults to NULL. + * @param object|string|null $timezone + * (optional) PHP DateTimeZone object, string or NULL allowed. Optionally + * force time to a specific timezone, defaults to user timezone, if set, + * otherwise site timezone. Defaults to NULL. + * + * @param bool $reset + * (optional) Static cache reset. * * @return object * The current time as a date object. */ -function date_now($timezone = NULL) { - return new DateObject('now', $timezone); +function date_now($timezone = NULL, $reset = FALSE) { + $static_var = __FUNCTION__ . $timezone; + if ($timezone instanceof DateTimeZone) { + $static_var = __FUNCTION__ . $timezone->getName(); + } + + if ($reset) { + drupal_static_reset($static_var); + } + + $now = &drupal_static($static_var); + + if (!isset($now)) { + $now = new DateObject('now', $timezone); + } + + // Avoid unexpected manipulation of cached $now object + // by subsequent code execution + // @see https://drupal.org/node/2261395 + $clone = clone $now; + return $clone; } /** @@ -1771,7 +1926,12 @@ function date_days_in_month($year, $month) { // Pick a day in the middle of the month to avoid timezone shifts. $datetime = date_pad($year, 4) . '-' . date_pad($month) . '-15 00:00:00'; $date = new DateObject($datetime); - return $date->format('t'); + if ($date->errors) { + return FALSE; + } + else { + return $date->format('t'); + } } /** @@ -1780,7 +1940,7 @@ function date_days_in_month($year, $month) { * @param mixed $date * (optional) The current date object, or a date string. Defaults to NULL. * - * @return integer + * @return int * The number of days in the year. */ function date_days_in_year($date = NULL) { @@ -1809,7 +1969,7 @@ function date_days_in_year($date = NULL) { * @param mixed $date * (optional) The current date object, or a date string. Defaults to NULL. * - * @return integer + * @return int * The number of ISO weeks in a year. */ function date_iso_weeks_in_year($date = NULL) { @@ -1901,7 +2061,7 @@ function date_week_range($week, $year) { // Move forwards to the last day of the week. $max_date = clone($min_date); - date_modify($max_date, '+7 days'); + date_modify($max_date, '+6 days'); if (date_format($min_date, 'Y') != $year) { $min_date = new DateObject($year . '-01-01 00:00:00'); @@ -1926,6 +2086,9 @@ function date_iso_week_range($week, $year) { date_timezone_set($min_date, date_default_timezone_object()); // Find the first day of the first ISO week in the year. + // If it's already a Monday, date_modify won't add a Monday, + // it will remain the same day. So add a Sunday first, then a Monday. + date_modify($min_date, '+1 Sunday'); date_modify($min_date, '+1 Monday'); // Jump ahead to the desired week for the beginning of the week range. @@ -1935,7 +2098,7 @@ function date_iso_week_range($week, $year) { // Move forwards to the last day of the week. $max_date = clone($min_date); - date_modify($max_date, '+7 days'); + date_modify($max_date, '+6 days'); return array($min_date, $max_date); } @@ -2043,7 +2206,8 @@ function date_has_time($granularity) { if (!is_array($granularity)) { $granularity = array(); } - return (bool) count(array_intersect($granularity, array('hour', 'minute', 'second'))); + $options = array('hour', 'minute', 'second'); + return (bool) count(array_intersect($granularity, $options)); } /** @@ -2059,7 +2223,8 @@ function date_has_date($granularity) { if (!is_array($granularity)) { $granularity = array(); } - return (bool) count(array_intersect($granularity, array('year', 'month', 'day'))); + $options = array('year', 'month', 'day'); + return (bool) count(array_intersect($granularity, $options)); } /** @@ -2077,8 +2242,10 @@ function date_part_format($part, $format) { switch ($part) { case 'date': return date_limit_format($format, array('year', 'month', 'day')); + case 'time': return date_limit_format($format, array('hour', 'minute', 'second')); + default: return date_limit_format($format, array($part)); } @@ -2100,6 +2267,17 @@ function date_part_format($part, $format) { * The format string with all other elements removed. */ function date_limit_format($format, $granularity) { + // Use the advanced drupal_static() pattern to improve performance. + static $drupal_static_fast; + if (!isset($drupal_static_fast)) { + $drupal_static_fast['formats'] = &drupal_static(__FUNCTION__); + } + $formats = &$drupal_static_fast['formats']; + $format_granularity_cid = $format . '|' . implode(',', $granularity); + if (isset($formats[$format_granularity_cid])) { + return $formats[$format_granularity_cid]; + } + // If punctuation has been escaped, remove the escaping. Done using strtr() // because it is easier than getting the escape character extracted using // preg_replace(). @@ -2129,21 +2307,27 @@ function date_limit_format($format, $granularity) { case 'year': $regex[] = '([\-/\.,:]?\s?(? "$path/theme", ); return array( - 'date_nav_title' => $base + array('variables' => array('granularity' => NULL, 'view' => NULL, 'link' => NULL, 'format' => NULL)), + 'date_nav_title' => $base + array( + 'variables' => array( + 'granularity' => NULL, 'view' => NULL, 'link' => NULL, 'format' => NULL + ), + ), 'date_timezone' => $base + array('render element' => 'element'), 'date_select' => $base + array('render element' => 'element'), 'date_text' => $base + array('render element' => 'element'), @@ -2290,7 +2495,11 @@ function date_api_theme($existing, $type, $theme, $path) { 'date_part_label_time' => $base + array('variables' => array('date_part' => NULL, 'element' => NULL)), 'date_views_filter_form' => $base + array('template' => 'date-views-filter-form', 'render element' => 'form'), 'date_calendar_day' => $base + array('variables' => array('date' => NULL)), - 'date_time_ago' => $base + array('variables' => array('start_date' => NULL, 'end_date' => NULL, 'interval' => NULL)), + 'date_time_ago' => $base + array( + 'variables' => array( + 'start_date' => NULL, 'end_date' => NULL, 'interval' => NULL + ), + ), ); } @@ -2310,9 +2519,11 @@ function date_get_timezone($handling, $timezone = '') { case 'date': $timezone = !empty($timezone) ? $timezone : date_default_timezone(); break; + case 'utc': $timezone = 'UTC'; break; + default: $timezone = date_default_timezone(); } @@ -2320,26 +2531,40 @@ function date_get_timezone($handling, $timezone = '') { } /** - * Function to figure out which db timezone applies to a date and select it. + * Function to figure out which db timezone applies to a date. * * @param string $handling * The timezone handling. * @param string $timezone - * (optional) A timezone string. Defaults to an empty string. + * (optional) When $handling is 'date', date_get_timezone_db() returns this + * value. * * @return string * The timezone string. */ -function date_get_timezone_db($handling, $timezone = '') { +function date_get_timezone_db($handling, $timezone = NULL) { switch ($handling) { - case 'none': - $timezone = date_default_timezone(); - break; - default: + case ('utc'): + case ('site'): + case ('user'): + // These handling modes all convert to UTC before storing in the DB. $timezone = 'UTC'; break; + + case ('date'): + if ($timezone == NULL) { + // This shouldn't happen, since it's meaning is undefined. But we need + // to fall back to *something* that's a legal timezone. + $timezone = date_default_timezone(); + } + break; + + case ('none'): + default: + $timezone = date_default_timezone(); + break; } - return $timezone > '' ? $timezone : 'UTC'; + return $timezone; } /** @@ -2388,12 +2613,12 @@ function date_order() { * TRUE if the date range is valid, FALSE otherwise. */ function date_range_valid($string) { - $matches = preg_match('@^(\-[0-9]+|[0-9]{4}):([\+|\-][0-9]+|[0-9]{4})$@', $string); + $matches = preg_match('@^([\+\-][0-9]+|[0-9]{4}):([\+\-][0-9]+|[0-9]{4})$@', $string); return $matches < 1 ? FALSE : TRUE; } /** - * Splits a string like -3:+3 or 2001:2010 into an array of min and max years. + * Splits a string like -3:+3 or 2001:2010 into an array of start and end years. * * Center the range around the current year, if any, but expand it far * enough so it will pick up the year value in the field in case @@ -2405,45 +2630,44 @@ function date_range_valid($string) { * (optional) A date object. Defaults to NULL. * * @return array - * A numerically indexed array, containing a minimum and maximum year. + * A numerically indexed array, containing a start and end year. */ function date_range_years($string, $date = NULL) { $this_year = date_format(date_now(), 'Y'); - list($min_year, $max_year) = explode(':', $string); + list($start_year, $end_year) = explode(':', $string); // Valid patterns would be -5:+5, 0:+1, 2008:2010. - $plus_pattern = '@[\+|\-][0-9]{1,4}@'; + $plus_pattern = '@[\+\-][0-9]{1,4}@'; $year_pattern = '@^[0-9]{4}@'; - if (!preg_match($year_pattern, $min_year, $matches)) { - if (preg_match($plus_pattern, $min_year, $matches)) { - $min_year = $this_year + $matches[0]; + if (!preg_match($year_pattern, $start_year, $matches)) { + if (preg_match($plus_pattern, $start_year, $matches)) { + $start_year = $this_year + $matches[0]; } else { - $min_year = $this_year; + $start_year = $this_year; } } - if (!preg_match($year_pattern, $max_year, $matches)) { - if (preg_match($plus_pattern, $max_year, $matches)) { - $max_year = $this_year + $matches[0]; + if (!preg_match($year_pattern, $end_year, $matches)) { + if (preg_match($plus_pattern, $end_year, $matches)) { + $end_year = $this_year + $matches[0]; } else { - $max_year = $this_year; + $end_year = $this_year; } } - // We expect the $min year to be less than the $max year. - // Some custom values for -99:+99 might not obey that. - if ($min_year > $max_year) { - $temp = $max_year; - $max_year = $min_year; - $min_year = $temp; - } // If there is a current value, stretch the range to include it. $value_year = is_object($date) ? $date->format('Y') : ''; if (!empty($value_year)) { - $min_year = min($value_year, $min_year); - $max_year = max($value_year, $max_year); + if ($start_year <= $end_year) { + $start_year = min($value_year, $start_year); + $end_year = max($value_year, $end_year); + } + else { + $start_year = max($value_year, $start_year); + $end_year = min($value_year, $end_year); + } } - return array($min_year, $max_year); + return array($start_year, $end_year); } /** @@ -2603,6 +2827,7 @@ function date_is_all_day($string1, $string2, $granularity = 'second', $increment || ($hour2 == 23 && in_array($min2, array($max_minutes, 59)) && in_array($sec2, array($max_seconds, 59))) || ($hour1 == 0 && $hour2 == 0 && $min1 == 0 && $min2 == 0 && $sec1 == 0 && $sec2 == 0); break; + case 'minute': $min_match = $time1 == '00:00:00' || ($hour1 == 0 && $min1 == 0); @@ -2610,6 +2835,7 @@ function date_is_all_day($string1, $string2, $granularity = 'second', $increment || ($hour2 == 23 && in_array($min2, array($max_minutes, 59))) || ($hour1 == 0 && $hour2 == 0 && $min1 == 0 && $min2 == 0); break; + case 'hour': $min_match = $time1 == '00:00:00' || ($hour1 == 0); @@ -2617,6 +2843,7 @@ function date_is_all_day($string1, $string2, $granularity = 'second', $increment || ($hour2 == 23) || ($hour1 == 0 && $hour2 == 0); break; + default: $min_match = TRUE; $max_match = FALSE; @@ -2677,15 +2904,21 @@ function date_is_date($date) { } /** - * This function will replace ISO values that have the pattern 9999-00-00T00:00:00 - * with a pattern like 9999-01-01T00:00:00, to match the behavior of non-ISO - * dates and ensure that date objects created from this value contain a valid month - * and day. Without this fix, the ISO date '2020-00-00T00:00:00' would be created as + * Replace specific ISO values using patterns. + * + * Function will replace ISO values that have the pattern 9999-00-00T00:00:00 + * with a pattern like 9999-01-01T00:00:00, to match the behavior of non-ISO dates + * and ensure that date objects created from this value contain a valid month + * and day. + * Without this fix, the ISO date '2020-00-00T00:00:00' would be created as * November 30, 2019 (the previous day in the previous month). * * @param string $iso_string * An ISO string that needs to be made into a complete, valid date. * + * @return mixed|string + * replaced value, or incoming value. + * * @TODO Expand on this to work with all sorts of partial ISO dates. */ function date_make_iso_valid($iso_string) { diff --git a/sites/all/modules/contrib/fields/date/date_api/date_api_elements.inc b/sites/all/modules/contrib/fields/date/date_api/date_api_elements.inc index 7da4e58..638fcda 100644 --- a/sites/all/modules/contrib/fields/date/date_api/date_api_elements.inc +++ b/sites/all/modules/contrib/fields/date/date_api/date_api_elements.inc @@ -111,20 +111,24 @@ function date_default_date($element) { $format = DATE_FORMAT_DATETIME; // The text and popup widgets might return less than a full datetime string. - if (strlen($element['#default_value']) < 19) { + if (is_string($element['#default_value']) && strlen($element['#default_value']) < 19) { switch (strlen($element['#default_value'])) { case 16: $format = 'Y-m-d H:i'; break; + case 13: $format = 'Y-m-d H'; break; + case 10: $format = 'Y-m-d'; break; + case 7: $format = 'Y-m'; break; + case 4: $format = 'Y'; break; @@ -170,7 +174,7 @@ function date_year_range_element_process($element, &$form_state, $form) { $element['#attached']['js'][] = drupal_get_path('module', 'date_api') . '/date_year_range.js'; $context = array( - 'form' => $form, + 'form' => $form, ); drupal_alter('date_year_range_process', $element, $form_state, $context); @@ -239,7 +243,8 @@ function date_timezone_element_process($element, &$form_state, $form) { $label = theme('date_part_label_timezone', array('part_type' => 'select', 'element' => $element)); $element['timezone'] = array( '#type' => 'select', - '#title' => $element['#date_label_position'] == 'above' ? $label : '', + '#title' => $label, + '#title_display' => $element['#date_label_position'] == 'above' ? 'before' : 'invisible', '#options' => date_timezone_names($element['#required']), '#value' => $element['#value'], '#weight' => $element['#weight'], @@ -255,7 +260,7 @@ function date_timezone_element_process($element, &$form_state, $form) { } $context = array( - 'form' => $form, + 'form' => $form, ); drupal_alter('date_timezone_process', $element, $form_state, $context); @@ -263,7 +268,7 @@ function date_timezone_element_process($element, &$form_state, $form) { } /** - * Validation for timezone input + * Validation for timezone input. * * Move the timezone value from the nested field back to the original field. */ @@ -306,7 +311,6 @@ function date_text_element_value_callback($element, $input = FALSE, &$form_state * * The exact parts displayed in the field are those in #date_granularity. * The display of each part comes from #date_format. - * */ function date_text_element_process($element, &$form_state, $form) { if (date_hidden_element($element)) { @@ -315,14 +319,25 @@ function date_text_element_process($element, &$form_state, $form) { $element['#tree'] = TRUE; $element['#theme_wrappers'] = array('date_text'); - $element['date']['#value'] = $element['#value']['date']; + $element['date']['#value'] = isset($element['#value']['date']) ? $element['#value']['date'] : ''; $element['date']['#type'] = 'textfield'; $element['date']['#weight'] = !empty($element['date']['#weight']) ? $element['date']['#weight'] : $element['#weight']; $element['date']['#attributes'] = array('class' => isset($element['#attributes']['class']) ? $element['#attributes']['class'] += array('date-date') : array('date-date')); $now = date_example_date(); - $element['date']['#description'] = ' ' . t('Format: @date', array('@date' => date_format_date(date_example_date(), 'custom', $element['#date_format']))); + $element['date']['#title'] = t('Date'); + $element['date']['#title_display'] = 'invisible'; + $element['date']['#description'] = ' ' . t('Format: @date', array( + '@date' => date_format_date(date_example_date(), 'custom', $element['#date_format'] + ))); $element['date']['#ajax'] = !empty($element['#ajax']) ? $element['#ajax'] : FALSE; + // Make changes if instance is set to be rendered as a regular field. + if (!empty($element['#instance']['widget']['settings']['no_fieldset']) && $element['#field']['cardinality'] == 1) { + $element['date']['#title'] = check_plain($element['#instance']['label']); + $element['date']['#title_display'] = $element['#title_display']; + $element['date']['#required'] = $element['#required']; + } + // Keep the system from creating an error message for the sub-element. // We'll set our own message on the parent element. // $element['date']['#required'] = $element['#required']; @@ -338,7 +353,7 @@ function date_text_element_process($element, &$form_state, $form) { } $context = array( - 'form' => $form, + 'form' => $form, ); drupal_alter('date_text_process', $element, $form_state, $context); @@ -346,12 +361,11 @@ function date_text_element_process($element, &$form_state, $form) { } /** - * Validation for text input. + * Validation for text input. * * When used as a Views widget, the validation step always gets triggered, * even with no form submission. Before form submission $element['#value'] * contains a string, after submission it contains an array. - * */ function date_text_validate($element, &$form_state) { if (date_hidden_element($element)) { @@ -364,6 +378,11 @@ function date_text_validate($element, &$form_state) { $input_exists = NULL; $input = drupal_array_get_nested_value($form_state['values'], $element['#parents'], $input_exists); + // Trim extra spacing off user input of text fields. + if (isset($input['date'])) { + $input['date'] = trim($input['date']); + } + drupal_alter('date_text_pre_validate', $element, $form_state, $input); $label = !empty($element['#date_title']) ? $element['#date_title'] : (!empty($element['#title']) ? $element['#title'] : ''); @@ -418,7 +437,14 @@ function date_text_input_date($element, $input) { * Element value callback for date_select element. */ function date_select_element_value_callback($element, $input = FALSE, &$form_state = array()) { - $return = array('year' => '', 'month' => '', 'day' => '', 'hour' => '', 'minute' => '', 'second' => ''); + $return = array( + 'year' => '', + 'month' => '', + 'day' => '', + 'hour' => '', + 'minute' => '', + 'second' => '', + ); $date = NULL; if ($input !== FALSE) { $return = $input; @@ -428,7 +454,14 @@ function date_select_element_value_callback($element, $input = FALSE, &$form_sta $date = date_default_date($element); } $granularity = date_format_order($element['#date_format']); - $formats = array('year' => 'Y', 'month' => 'n', 'day' => 'j', 'hour' => 'H', 'minute' => 'i', 'second' => 's'); + $formats = array( + 'year' => 'Y', + 'month' => 'n', + 'day' => 'j', + 'hour' => 'H', + 'minute' => 'i', + 'second' => 's', + ); foreach ($granularity as $field) { if ($field != 'timezone') { $return[$field] = date_is_date($date) ? $date->format($formats[$field]) : ''; @@ -446,7 +479,6 @@ function date_select_element_value_callback($element, $input = FALSE, &$form_sta * * The exact parts displayed in the field are those in #date_granularity. * The display of each part comes from ['#date_settings']['format']. - * */ function date_select_element_process($element, &$form_state, $form) { if (date_hidden_element($element)) { @@ -470,7 +502,14 @@ function date_select_element_process($element, &$form_state, $form) { // Store a hidden value for all date parts not in the current display. $granularity = date_format_order($element['#date_format']); - $formats = array('year' => 'Y', 'month' => 'n', 'day' => 'j', 'hour' => 'H', 'minute' => 'i', 'second' => 's'); + $formats = array( + 'year' => 'Y', + 'month' => 'n', + 'day' => 'j', + 'hour' => 'H', + 'minute' => 'i', + 'second' => 's', + ); foreach (date_nongranularity($granularity) as $field) { if ($field != 'timezone') { $element[$field] = array( @@ -487,7 +526,7 @@ function date_select_element_process($element, &$form_state, $form) { } $context = array( - 'form' => $form, + 'form' => $form, ); drupal_alter('date_select_process', $element, $form_state, $context); @@ -518,7 +557,7 @@ function date_parts_element($element, $date, $format) { $sub_element = array('#granularity' => $granularity); $order = array_flip($granularity); - $hours_format = strpos(strtolower($element['#date_format']), 'a') ? 'g': 'G'; + $hours_format = strpos(strtolower($element['#date_format']), 'a') ? 'g' : 'G'; $month_function = strpos($element['#date_format'], 'F') !== FALSE ? 'date_month_names' : 'date_month_names_abbr'; $count = 0; $increment = min(intval($element['#date_increment']), 1); @@ -536,26 +575,29 @@ function date_parts_element($element, $date, $format) { switch ($field) { case 'year': $range = date_range_years($element['#date_year_range'], $date); - $min_year = $range[0]; - $max_year = $range[1]; + $start_year = $range[0]; + $end_year = $range[1]; $sub_element[$field]['#default_value'] = is_object($date) ? $date->format('Y') : ''; if ($part_type == 'select') { - $sub_element[$field]['#options'] = drupal_map_assoc(date_years($min_year, $max_year, $part_required)); + $sub_element[$field]['#options'] = drupal_map_assoc(date_years($start_year, $end_year, $part_required)); } break; + case 'month': $sub_element[$field]['#default_value'] = is_object($date) ? $date->format('n') : ''; if ($part_type == 'select') { $sub_element[$field]['#options'] = $month_function($part_required); } break; + case 'day': $sub_element[$field]['#default_value'] = is_object($date) ? $date->format('j') : ''; if ($part_type == 'select') { $sub_element[$field]['#options'] = drupal_map_assoc(date_days($part_required)); } break; + case 'hour': $sub_element[$field]['#default_value'] = is_object($date) ? $date->format($hours_format) : ''; if ($part_type == 'select') { @@ -563,6 +605,7 @@ function date_parts_element($element, $date, $format) { } $sub_element[$field]['#prefix'] = theme('date_part_hour_prefix', $element); break; + case 'minute': $sub_element[$field]['#default_value'] = is_object($date) ? $date->format('i') : ''; if ($part_type == 'select') { @@ -570,6 +613,7 @@ function date_parts_element($element, $date, $format) { } $sub_element[$field]['#prefix'] = theme('date_part_minsec_prefix', $element); break; + case 'second': $sub_element[$field]['#default_value'] = is_object($date) ? $date->format('s') : ''; if ($part_type == 'select') { @@ -585,6 +629,8 @@ function date_parts_element($element, $date, $format) { $sub_element[$field]['#type'] = 'textfield'; $sub_element[$field]['#theme'] = 'date_textfield_element'; $sub_element[$field]['#size'] = 7; + $sub_element[$field]['#title'] = $label; + $sub_element[$field]['#title_display'] = in_array($element['#date_label_position'], array('within', 'none')) ? 'invisible' : 'before'; if ($element['#date_label_position'] == 'within') { if (!empty($sub_element[$field]['#options']) && is_array($sub_element[$field]['#options'])) { $sub_element[$field]['#options'] = array( @@ -594,20 +640,16 @@ function date_parts_element($element, $date, $format) { $sub_element[$field]['#default_value'] = '-' . $label; } } - elseif ($element['#date_label_position'] != 'none') { - $sub_element[$field]['#title'] = $label; - } } else { $sub_element[$field]['#type'] = 'select'; $sub_element[$field]['#theme'] = 'date_select_element'; + $sub_element[$field]['#title'] = $label; + $sub_element[$field]['#title_display'] = in_array($element['#date_label_position'], array('within', 'none')) ? 'invisible' : 'before'; if ($element['#date_label_position'] == 'within') { $sub_element[$field]['#options'] = array( '' => '-' . $label) + $sub_element[$field]['#options']; } - elseif ($element['#date_label_position'] != 'none') { - $sub_element[$field]['#title'] = $label; - } } } @@ -621,9 +663,12 @@ function date_parts_element($element, $date, $format) { } if (($hours_format == 'g' || $hours_format == 'h') && date_has_time($granularity)) { + $label = theme('date_part_label_ampm', array('part_type' => 'ampm', 'element' => $element)); $sub_element['ampm'] = array( '#type' => 'select', '#theme' => 'date_select_element', + '#title' => $label, + '#title_display' => in_array($element['#date_label_position'], array('within', 'none')) ? 'invisible' : 'before', '#default_value' => is_object($date) ? (date_format($date, 'G') >= 12 ? 'pm' : 'am') : '', '#options' => drupal_map_assoc(date_ampm($part_required)), '#required' => $part_required, @@ -631,10 +676,7 @@ function date_parts_element($element, $date, $format) { '#attributes' => array('class' => array('date-ampm')), ); if ($element['#date_label_position'] == 'within') { - $sub_element['ampm']['#options'] = array('' => '-' . theme('date_part_label_ampm', array('part_type' => 'ampm', 'eleement' => $element))) + $sub_element['ampm']['#options']; - } - elseif ($element['#date_label_position'] != 'none') { - $sub_element['ampm']['#title'] = theme('date_part_label_ampm', array('part_type' => 'ampm', 'element' => $element)); + $sub_element['ampm']['#options'] = array('' => '-' . $label) + $sub_element['ampm']['#options']; } } diff --git a/sites/all/modules/contrib/fields/date/date_api/date_api_ical.inc b/sites/all/modules/contrib/fields/date/date_api/date_api_ical.inc index 2ca484e..4911298 100644 --- a/sites/all/modules/contrib/fields/date/date_api/date_api_ical.inc +++ b/sites/all/modules/contrib/fields/date/date_api/date_api_ical.inc @@ -181,6 +181,7 @@ function date_ical_parse($icaldatafolded = array()) { $parent[array_pop($parents)][] = array_pop($subgroups); } break; + // Add the timezones in with their index their TZID. case 'VTIMEZONE': $subgroup = end($subgroups); @@ -196,6 +197,7 @@ function date_ical_parse($icaldatafolded = array()) { array_pop($subgroups); array_pop($parents); break; + // Do some fun stuff with durations and all_day events and then append // to parent. case 'VEVENT': @@ -222,9 +224,9 @@ function date_ical_parse($icaldatafolded = array()) { // assumes the end date is inclusive. if (!empty($subgroup['DTEND']) && (!empty($subgroup['DTEND']['all_day']))) { // Make the end date one day earlier. - $date = new DateObject ($subgroup['DTEND']['datetime'] . ' 00:00:00', $subgroup['DTEND']['tz']); + $date = new DateObject($subgroup['DTEND']['datetime'] . ' 00:00:00', $subgroup['DTEND']['tz']); date_modify($date, '-1 day'); - $subgroup['DTEND']['datetime'] = date_format($date, 'Y-m-d'); + $subgroup['DTEND']['datetime'] = date_format($date, 'Y-m-d'); } // If a start datetime is defined AND there is no definition for // the end datetime THEN make the end datetime equal the start @@ -239,7 +241,7 @@ function date_ical_parse($icaldatafolded = array()) { if (!empty($subgroup['DTSTART']['all_day'])) { $subgroup['all_day'] = TRUE; } - // Add this element to the parent as an array under the + // Add this element to the parent as an array under the. prev($subgroups); $parent = &$subgroups[key($subgroups)]; @@ -264,12 +266,13 @@ function date_ical_parse($icaldatafolded = array()) { $field = !empty($matches[2]) ? $matches[2] : ''; $data = !empty($matches[3]) ? $matches[3] : ''; $parse_result = ''; + switch ($name) { // Keep blank lines out of the results. case '': break; - // Lots of properties have date values that must be parsed out. + // Lots of properties have date values that must be parsed out. case 'CREATED': case 'LAST-MODIFIED': case 'DTSTART': @@ -317,9 +320,9 @@ function date_ical_parse($icaldatafolded = array()) { $parse_result = date_ical_parse_location($field, $data); break; - // For all other properties, just store the property and the value. - // This can be expanded on in the future if other properties should - // be given special treatment. + // For all other properties, just store the property and the value. + // This can be expanded on in the future if other properties should + // be given special treatment. default: $parse_result = $data; break; @@ -360,7 +363,7 @@ function date_ical_parse($icaldatafolded = array()) { * has no timezone; the ical specs say no timezone * conversion should be done if no timezone info is * supplied - * @todo + * @todo * Another option for dates is the format PROPERTY;VALUE=PERIOD:XXXX. The * period may include a duration, or a date and a duration, or two dates, so * would have to be split into parts and run through date_ical_parse_date() @@ -401,6 +404,7 @@ function date_ical_parse_date($field, $data) { // Date. $datetime = date_pad($regs[1]) . '-' . date_pad($regs[2]) . '-' . date_pad($regs[3]); break; + case 'DATE-TIME': preg_match(DATE_REGEX_ICAL_DATETIME, $data, $regs); // Date. @@ -519,12 +523,12 @@ function date_ical_parse_duration(&$subgroup, $field = 'DURATION') { $data = $items['DATA']; preg_match('/^P(\d{1,4}[Y])?(\d{1,2}[M])?(\d{1,2}[W])?(\d{1,2}[D])?([T]{0,1})?(\d{1,2}[H])?(\d{1,2}[M])?(\d{1,2}[S])?/', $data, $duration); $items['year'] = isset($duration[1]) ? str_replace('Y', '', $duration[1]) : ''; - $items['month'] = isset($duration[2]) ?str_replace('M', '', $duration[2]) : ''; - $items['week'] = isset($duration[3]) ?str_replace('W', '', $duration[3]) : ''; - $items['day'] = isset($duration[4]) ?str_replace('D', '', $duration[4]) : ''; - $items['hour'] = isset($duration[6]) ?str_replace('H', '', $duration[6]) : ''; - $items['minute'] = isset($duration[7]) ?str_replace('M', '', $duration[7]) : ''; - $items['second'] = isset($duration[8]) ?str_replace('S', '', $duration[8]) : ''; + $items['month'] = isset($duration[2]) ? str_replace('M', '', $duration[2]) : ''; + $items['week'] = isset($duration[3]) ? str_replace('W', '', $duration[3]) : ''; + $items['day'] = isset($duration[4]) ? str_replace('D', '', $duration[4]) : ''; + $items['hour'] = isset($duration[6]) ? str_replace('H', '', $duration[6]) : ''; + $items['minute'] = isset($duration[7]) ? str_replace('M', '', $duration[7]) : ''; + $items['second'] = isset($duration[8]) ? str_replace('S', '', $duration[8]) : ''; $start_date = array_key_exists('DTSTART', $subgroup) ? $subgroup['DTSTART']['datetime'] : date_format(date_now(), DATE_FORMAT_ISO); $timezone = array_key_exists('DTSTART', $subgroup) ? $subgroup['DTSTART']['tz'] : variable_get('date_default_timezone'); if (empty($timezone)) { @@ -542,7 +546,7 @@ function date_ical_parse_duration(&$subgroup, $field = 'DURATION') { 'datetime' => date_format($date2, DATE_FORMAT_DATETIME), 'all_day' => isset($subgroup['DTSTART']['all_day']) ? $subgroup['DTSTART']['all_day'] : 0, 'tz' => $timezone, - ); + ); $duration = date_format($date2, 'U') - date_format($date, 'U'); $subgroup['DURATION'] = array('DATA' => $data, 'DURATION' => $duration); } @@ -631,7 +635,6 @@ function date_ical_date($ical_date, $to_tz = FALSE) { * * @return string * Escaped text - * */ function date_ical_escape_text($text) { $text = drupal_html_to_text($text); @@ -693,14 +696,14 @@ function date_ical_escape_text($text) { * ) */ function date_api_ical_build_rrule($form_values) { - $RRULE = ''; + $rrule = ''; if (empty($form_values) || !is_array($form_values)) { - return $RRULE; + return $rrule; } // Grab the RRULE data and put them into iCal RRULE format. - $RRULE .= 'RRULE:FREQ=' . (!array_key_exists('FREQ', $form_values) ? 'DAILY' : $form_values['FREQ']); - $RRULE .= ';INTERVAL=' . (!array_key_exists('INTERVAL', $form_values) ? 1 : $form_values['INTERVAL']); + $rrule .= 'RRULE:FREQ=' . (!array_key_exists('FREQ', $form_values) ? 'DAILY' : $form_values['FREQ']); + $rrule .= ';INTERVAL=' . (!array_key_exists('INTERVAL', $form_values) ? 1 : $form_values['INTERVAL']); // Unset the empty 'All' values. if (array_key_exists('BYDAY', $form_values) && is_array($form_values['BYDAY'])) { @@ -713,14 +716,14 @@ function date_api_ical_build_rrule($form_values) { unset($form_values['BYMONTHDAY']['']); } - if (array_key_exists('BYDAY', $form_values) && is_array($form_values['BYDAY']) && $BYDAY = implode(",", $form_values['BYDAY'])) { - $RRULE .= ';BYDAY=' . $BYDAY; + if (array_key_exists('BYDAY', $form_values) && is_array($form_values['BYDAY']) && $byday = implode(",", $form_values['BYDAY'])) { + $rrule .= ';BYDAY=' . $byday; } - if (array_key_exists('BYMONTH', $form_values) && is_array($form_values['BYMONTH']) && $BYMONTH = implode(",", $form_values['BYMONTH'])) { - $RRULE .= ';BYMONTH=' . $BYMONTH; + if (array_key_exists('BYMONTH', $form_values) && is_array($form_values['BYMONTH']) && $bymonth = implode(",", $form_values['BYMONTH'])) { + $rrule .= ';BYMONTH=' . $bymonth; } - if (array_key_exists('BYMONTHDAY', $form_values) && is_array($form_values['BYMONTHDAY']) && $BYMONTHDAY = implode(",", $form_values['BYMONTHDAY'])) { - $RRULE .= ';BYMONTHDAY=' . $BYMONTHDAY; + if (array_key_exists('BYMONTHDAY', $form_values) && is_array($form_values['BYMONTHDAY']) && $bymonthday = implode(",", $form_values['BYMONTHDAY'])) { + $rrule .= ';BYMONTHDAY=' . $bymonthday; } // The UNTIL date is supposed to always be expressed in UTC. // The input date values may already have been converted to a date object on a @@ -731,8 +734,17 @@ function date_api_ical_build_rrule($form_values) { if (!is_object($form_values['UNTIL']['datetime'])) { // If this is a date without time, give it time. if (strlen($form_values['UNTIL']['datetime']) < 11) { + $granularity_options = drupal_map_assoc(array( + 'year', + 'month', + 'day', + 'hour', + 'minute', + 'second', + )); + $form_values['UNTIL']['datetime'] .= ' 23:59:59'; - $form_values['UNTIL']['granularity'] = serialize(drupal_map_assoc(array('year', 'month', 'day', 'hour', 'minute', 'second'))); + $form_values['UNTIL']['granularity'] = serialize($granularity_options); $form_values['UNTIL']['all_day'] = FALSE; } $until = date_ical_date($form_values['UNTIL'], 'UTC'); @@ -740,21 +752,21 @@ function date_api_ical_build_rrule($form_values) { else { $until = $form_values['UNTIL']['datetime']; } - $RRULE .= ';UNTIL=' . date_format($until, DATE_FORMAT_ICAL) . 'Z'; + $rrule .= ';UNTIL=' . date_format($until, DATE_FORMAT_ICAL) . 'Z'; } // Our form doesn't allow a value for COUNT, but it may be needed by // modules using the API, so add it to the rule. if (array_key_exists('COUNT', $form_values)) { - $RRULE .= ';COUNT=' . $form_values['COUNT']; + $rrule .= ';COUNT=' . $form_values['COUNT']; } // iCal rules presume the week starts on Monday unless otherwise specified, // so we'll specify it. if (array_key_exists('WKST', $form_values)) { - $RRULE .= ';WKST=' . $form_values['WKST']; + $rrule .= ';WKST=' . $form_values['WKST']; } else { - $RRULE .= ';WKST=' . date_repeat_dow2day(variable_get('date_first_day', 0)); + $rrule .= ';WKST=' . date_repeat_dow2day(variable_get('date_first_day', 0)); } // Exceptions dates go last, on their own line. @@ -765,7 +777,7 @@ function date_api_ical_build_rrule($form_values) { foreach ($form_values['EXDATE'] as $value) { if (!empty($value['datetime'])) { $date = !is_object($value['datetime']) ? date_ical_date($value, 'UTC') : $value['datetime']; - $ex_date = !empty($date) ? date_format($date, DATE_FORMAT_ICAL) . 'Z': ''; + $ex_date = !empty($date) ? date_format($date, DATE_FORMAT_ICAL) . 'Z' : ''; if (!empty($ex_date)) { $ex_dates[] = $ex_date; } @@ -773,11 +785,11 @@ function date_api_ical_build_rrule($form_values) { } if (!empty($ex_dates)) { sort($ex_dates); - $RRULE .= chr(13) . chr(10) . 'EXDATE:' . implode(',', $ex_dates); + $rrule .= chr(13) . chr(10) . 'EXDATE:' . implode(',', $ex_dates); } } elseif (!empty($form_values['EXDATE'])) { - $RRULE .= chr(13) . chr(10) . 'EXDATE:' . $form_values['EXDATE']; + $rrule .= chr(13) . chr(10) . 'EXDATE:' . $form_values['EXDATE']; } // Exceptions dates go last, on their own line. @@ -785,19 +797,19 @@ function date_api_ical_build_rrule($form_values) { $ex_dates = array(); foreach ($form_values['RDATE'] as $value) { $date = !is_object($value['datetime']) ? date_ical_date($value, 'UTC') : $value['datetime']; - $ex_date = !empty($date) ? date_format($date, DATE_FORMAT_ICAL) . 'Z': ''; + $ex_date = !empty($date) ? date_format($date, DATE_FORMAT_ICAL) . 'Z' : ''; if (!empty($ex_date)) { $ex_dates[] = $ex_date; } } if (!empty($ex_dates)) { sort($ex_dates); - $RRULE .= chr(13) . chr(10) . 'RDATE:' . implode(',', $ex_dates); + $rrule .= chr(13) . chr(10) . 'RDATE:' . implode(',', $ex_dates); } } elseif (!empty($form_values['RDATE'])) { - $RRULE .= chr(13) . chr(10) . 'RDATE:' . $form_values['RDATE']; + $rrule .= chr(13) . chr(10) . 'RDATE:' . $form_values['RDATE']; } - return $RRULE; + return $rrule; } diff --git a/sites/all/modules/contrib/fields/date/date_api/date_api_sql.inc b/sites/all/modules/contrib/fields/date/date_api/date_api_sql.inc index a2adc65..a95ca4e 100644 --- a/sites/all/modules/contrib/fields/date/date_api/date_api_sql.inc +++ b/sites/all/modules/contrib/fields/date/date_api/date_api_sql.inc @@ -20,17 +20,17 @@ * Correct sql string for database type. */ function date_sql_concat($array) { - switch (db_driver()) { + switch (Database::getConnection()->databaseType()) { case 'mysql': - case 'mysqli': return "CONCAT(" . implode(",", $array) . ")"; + case 'pgsql': return implode(" || ", $array); } } /** - * Helper function to do cross-database NULL replacements + * Helper function to do cross-database NULL replacements. * * @param array $array * An array of values to test for NULL values. @@ -39,9 +39,8 @@ function date_sql_concat($array) { * SQL statement to return the first non-NULL value in the list. */ function date_sql_coalesce($array) { - switch (db_driver()) { + switch (Database::getConnection()->databaseType()) { case 'mysql': - case 'mysqli': case 'pgsql': return "COALESCE(" . implode(',', $array) . ")"; } @@ -63,6 +62,7 @@ function date_sql_pad($str, $size = 2, $pad = '0', $side = 'l') { switch ($side) { case 'r': return "RPAD($str, $size, '$pad')"; + default: return "LPAD($str, $size, '$pad')"; } @@ -71,6 +71,7 @@ function date_sql_pad($str, $size = 2, $pad = '0', $side = 'l') { /** * A class to manipulate date SQL. */ +// @codingStandardsIgnoreStart class date_sql_handler { var $db_type = NULL; var $date_type = DATE_DATETIME; @@ -88,8 +89,8 @@ class date_sql_handler { /** * The object constuctor. */ - function __construct($date_type = DATE_DATETIME, $local_timezone = NULL, $offset = '+00:00') { - $this->db_type = db_driver(); + public function __construct($date_type = DATE_DATETIME, $local_timezone = NULL, $offset = '+00:00') { + $this->db_type = Database::getConnection()->databaseType(); $this->date_type = $date_type; $this->db_timezone = 'UTC'; $this->local_timezone = isset($local_timezone) ? $local_timezone : date_default_timezone(); @@ -99,18 +100,18 @@ class date_sql_handler { /** * See if the db has timezone name support. */ - function db_tz_support($reset = FALSE) { + public function db_tz_support($reset = FALSE) { $has_support = variable_get('date_db_tz_support', -1); if ($has_support == -1 || $reset) { $has_support = FALSE; switch ($this->db_type) { case 'mysql': - case 'mysqli': $test = db_query("SELECT CONVERT_TZ('2008-02-15 12:00:00', 'UTC', 'US/Central')")->fetchField(); if ($test == '2008-02-15 06:00:00') { $has_support = TRUE; } break; + case 'pgsql': $test = db_query("SELECT '2008-02-15 12:00:00 UTC' AT TIME ZONE 'US/Central'")->fetchField(); if ($test == '2008-02-15 06:00:00') { @@ -139,18 +140,19 @@ class date_sql_handler { * set a fixed offset, not a timezone, so any value other than * '+00:00' should be used with caution. */ - function set_db_timezone($offset = '+00:00') { + public function set_db_timezone($offset = '+00:00') { static $already_set = FALSE; - $type = db_driver(); + $type = Database::getConnection()->databaseType(); if (!$already_set) { switch ($type) { case 'mysql': - case 'mysqli': db_query("SET @@session.time_zone = '$offset'"); break; + case 'pgsql': db_query("SET TIME ZONE INTERVAL '$offset' HOUR TO MINUTE"); break; + case 'sqlsrv': // Issue #1201342, This is the wrong way to set the timezone, this // still needs to be fixed. In the meantime, commenting this out makes @@ -165,7 +167,7 @@ class date_sql_handler { /** * Return timezone offset for the date being processed. */ - function get_offset($comp_date = NULL) { + public function get_offset($comp_date = NULL) { if (!empty($this->db_timezone) && !empty($this->local_timezone)) { if ($this->db_timezone != $this->local_timezone) { if (empty($comp_date)) { @@ -199,52 +201,61 @@ class date_sql_handler { } switch ($this->db_type) { case 'mysql': - case 'mysqli': switch ($this->date_type) { case DATE_UNIX: $field = "FROM_UNIXTIME($field)"; break; + case DATE_ISO: $field = "STR_TO_DATE($field, '%Y-%m-%dT%T')"; break; + case DATE_DATETIME: break; } break; + case 'pgsql': switch ($this->date_type) { case DATE_UNIX: $field = "$field::ABSTIME"; break; + case DATE_ISO: $field = "TO_DATE($field, 'FMYYYY-FMMM-FMDDTFMHH24:FMMI:FMSS')"; break; + case DATE_DATETIME: break; } break; + case 'sqlite': switch ($this->date_type) { case DATE_UNIX: $field = "datetime($field, 'unixepoch')"; break; + case DATE_ISO: case DATE_DATETIME: $field = "datetime($field)"; break; } break; + case 'sqlsrv': switch ($this->date_type) { case DATE_UNIX: $field = "DATEADD(s, $field, '19700101 00:00:00:000')"; break; + case DATE_ISO: case DATE_DATETIME: $field = "CAST($field as smalldatetime)"; break; } break; + break; } // Adjust the resulting value to the right timezone/offset. @@ -258,12 +269,14 @@ class date_sql_handler { if (!empty($offset)) { switch ($this->db_type) { case 'mysql': - case 'mysqli': return "ADDTIME($field, SEC_TO_TIME($offset))"; + case 'pgsql': - return "($field + INTERVAL '$offset SECONDS')";; + return "($field + INTERVAL '$offset SECONDS')"; + case 'sqlite': return "datetime($field, '$offset seconds')"; + case 'sqlsrv': return "DATEADD(second, $offset, $field)"; } @@ -288,10 +301,10 @@ class date_sql_handler { $granularity = strtoupper($granularity); switch ($this->db_type) { case 'mysql': - case 'mysqli': switch ($direction) { case 'ADD': return "DATE_ADD($field, INTERVAL $count $granularity)"; + case 'SUB': return "DATE_SUB($field, INTERVAL $count $granularity)"; } @@ -301,6 +314,7 @@ class date_sql_handler { switch ($direction) { case 'ADD': return "($field + INTERVAL '$count $granularity')"; + case 'SUB': return "($field - INTERVAL '$count $granularity')"; } @@ -309,6 +323,7 @@ class date_sql_handler { switch ($direction) { case 'ADD': return "datetime($field, '+$count $granularity')"; + case 'SUB': return "datetime($field, '-$count $granularity')"; } @@ -358,8 +373,8 @@ class date_sql_handler { else { switch ($this->db_type) { case 'mysql': - case 'mysqli': return "CONVERT_TZ($field, $db_zone, $localzone)"; + case 'pgsql': // WITH TIME ZONE assumes the date is using the system // timezone, which should have been set to UTC. @@ -382,7 +397,6 @@ class date_sql_handler { function sql_format($format, $field) { switch ($this->db_type) { case 'mysql': - case 'mysqli': $replace = array( 'Y' => '%Y', 'y' => '%y', @@ -404,6 +418,7 @@ class date_sql_handler { ); $format = strtr($format, $replace); return "DATE_FORMAT($field, '$format')"; + case 'pgsql': $replace = array( 'Y' => 'YYYY', @@ -430,6 +445,7 @@ class date_sql_handler { ); $format = strtr($format, $replace); return "TO_CHAR($field, '$format')"; + case 'sqlite': $replace = array( // 4 digit year number. @@ -469,6 +485,7 @@ class date_sql_handler { ); $format = strtr($format, $replace); return "strftime('$format', $field)"; + case 'sqlsrv': $replace = array( // 4 digit year number. @@ -537,44 +554,51 @@ class date_sql_handler { switch (strtoupper($extract_type)) { case 'DATE': return $field; + case 'YEAR': return "EXTRACT(YEAR FROM($field))"; + case 'MONTH': return "EXTRACT(MONTH FROM($field))"; + case 'DAY': return "EXTRACT(DAY FROM($field))"; + case 'HOUR': return "EXTRACT(HOUR FROM($field))"; + case 'MINUTE': return "EXTRACT(MINUTE FROM($field))"; + case 'SECOND': return "EXTRACT(SECOND FROM($field))"; + // ISO week number for date. case 'WEEK': switch ($this->db_type) { case 'mysql': - case 'mysqli': // WEEK using arg 3 in MySQl should return the same value as // Postgres EXTRACT. return "WEEK($field, 3)"; + case 'pgsql': return "EXTRACT(WEEK FROM($field))"; } case 'DOW': switch ($this->db_type) { case 'mysql': - case 'mysqli': // MySQL returns 1 for Sunday through 7 for Saturday, PHP date // functions and Postgres use 0 for Sunday and 6 for Saturday. return "INTEGER(DAYOFWEEK($field) - 1)"; + case 'pgsql': return "EXTRACT(DOW FROM($field))"; } case 'DOY': switch ($this->db_type) { case 'mysql': - case 'mysqli': return "DAYOFYEAR($field)"; + case 'pgsql': return "EXTRACT(DOY FROM($field))"; } @@ -787,8 +811,7 @@ class date_sql_handler { } /** - * Create a complete datetime value out of an - * incomplete array of selected values. + * Create a complete date/time value out of an incomplete array of values. * * For example, array('year' => 2008, 'month' => 05) will fill * in the day, hour, minute and second with the earliest possible @@ -807,9 +830,11 @@ class date_sql_handler { case 'empty_min': case 'min': return date_format($dates[0], 'Y-m-d H:i:s'); + case 'empty_max': case 'max': return date_format($dates[1], 'Y-m-d H:i:s'); + default: return; } @@ -852,7 +877,7 @@ class date_sql_handler { } /** - * A function to test the validity of various date parts + * A function to test the validity of various date parts. */ function part_is_valid($value, $type) { if (!preg_match('/^[0-9]*$/', $value)) { @@ -868,16 +893,19 @@ class date_sql_handler { return FALSE; } break; + case 'month': if ($value < 0 || $value > 12) { return FALSE; } break; + case 'day': if ($value < 0 || $value > 31) { return FALSE; } break; + case 'week': if ($value < 0 || $value > 53) { return FALSE; @@ -896,32 +924,42 @@ class date_sql_handler { } $formats = array('display', 'sql'); // Start with the site long date format and add seconds to it. - $long = str_replace(':i', ':i:s', variable_get('date_format_long', 'l, F j, Y - H:i')); + $short = str_replace(':i', ':i:s', variable_get('date_format_short', 'l, F j, Y - H:i')); switch ($granularity) { case 'year': $formats['display'] = 'Y'; $formats['sql'] = 'Y'; break; + case 'month': - $formats['display'] = date_limit_format($long, array('year', 'month')); + $formats['display'] = date_limit_format($short, array('year', 'month')); $formats['sql'] = 'Y-m'; break; + case 'day': - $formats['display'] = date_limit_format($long, array('year', 'month', 'day')); + $args = array('year', 'month', 'day'); + $formats['display'] = date_limit_format($short, $args); $formats['sql'] = 'Y-m-d'; break; + case 'hour': - $formats['display'] = date_limit_format($long, array('year', 'month', 'day', 'hour')); + $args = array('year', 'month', 'day', 'hour'); + $formats['display'] = date_limit_format($short, $args); $formats['sql'] = 'Y-m-d\TH'; break; + case 'minute': - $formats['display'] = date_limit_format($long, array('year', 'month', 'day', 'hour', 'minute')); + $args = array('year', 'month', 'day', 'hour', 'minute'); + $formats['display'] = date_limit_format($short, $args); $formats['sql'] = 'Y-m-d\TH:i'; break; + case 'second': - $formats['display'] = date_limit_format($long, array('year', 'month', 'day', 'hour', 'minute', 'second')); + $args = array('year', 'month', 'day', 'hour', 'minute', 'second'); + $formats['display'] = date_limit_format($short, $args); $formats['sql'] = 'Y-m-d\TH:i:s'; break; + case 'week': $formats['display'] = 'F j Y (W)'; $formats['sql'] = 'Y-\WW'; @@ -939,7 +977,7 @@ class date_sql_handler { '#type' => 'radios', '#default_value' => $granularity, '#options' => $this->date_parts(), - ); + ); return $form; } @@ -1042,7 +1080,6 @@ class date_sql_handler { $direction = $results[1]; $count = $results[2]; $item = $results[3]; - $replace = array( 'now' => '@', '+' => 'P', @@ -1063,14 +1100,27 @@ class date_sql_handler { 'second' => 'S', ' ' => '', ' ' => '', - ); - $prefix = in_array($item, array('hours', 'hour', 'minutes', 'minute', 'seconds', 'second')) ? 'T' : ''; - return $prefix . strtr($direction, $replace) . $count . strtr($item, $replace); + ); + $args = array('hours', 'hour', 'minutes', 'minute', 'seconds', 'second'); + if (in_array($item, $args)) { + $prefix = 'T'; + } + else { + $prefix = ''; + } + $return = $prefix; + $return .= strtr($direction, $replace); + $return .= $count; + $return .= strtr($item, $replace); + + return $return; } /** - * Use the parsed values from the ISO argument to determine the - * granularity of this period. + * Granularity arguments handler. + * + * Use the parsed values from the ISO argument + * to determine the granularity of this period. */ function arg_granularity($arg) { $granularity = ''; @@ -1149,8 +1199,9 @@ class date_sql_handler { } return array($min_date, $max_date); } - // Intercept invalid info and fall back to the current date. + // Intercept invalid info and fall back to the current date. $now = date_now(); return array($now, $now); } } +// @codingStandardsIgnoreEnd diff --git a/sites/all/modules/contrib/fields/date/date_api/theme/theme.inc b/sites/all/modules/contrib/fields/date/date_api/theme/theme.inc index 032e3e9..0a57ecd 100644 --- a/sites/all/modules/contrib/fields/date/date_api/theme/theme.inc +++ b/sites/all/modules/contrib/fields/date/date_api/theme/theme.inc @@ -194,6 +194,7 @@ function theme_date_calendar_day($variables) { function theme_date_time_ago($variables) { $start_date = $variables['start_date']; $end_date = $variables['end_date']; + $use_end_date = isset($variables['use_end_date']) ? $variables['use_end_date'] : false; $interval = !empty($variables['interval']) ? $variables['interval'] : 2; $display = isset($variables['interval_display']) ? $variables['interval_display'] : 'time ago'; @@ -202,28 +203,43 @@ function theme_date_time_ago($variables) { return; } - // Time to compare dates to. - $now = date_format(date_now(), DATE_FORMAT_UNIX); - $start = date_format($start_date, DATE_FORMAT_UNIX); + // We use the end date only when the option is checked. + if ($use_end_date){ + $date = date_format($end_date, DATE_FORMAT_UNIX); + } + else { + $date = date_format($start_date, DATE_FORMAT_UNIX); + } - // will be positive for a datetime in the past (ago), and negative for a datetime in the future (hence) - $time_diff = $now - $start; + // Time to compare dates to. + + $now = date_format(date_now(), DATE_FORMAT_UNIX); + + // Will be positive for a datetime in the past (ago), and negative for a datetime in the future (hence). + $time_diff = $now - $date; // Uses the same options used by Views format_interval. switch ($display) { case 'raw time ago': return format_interval($time_diff, $interval); + case 'time ago': return t('%time ago', array('%time' => format_interval($time_diff, $interval))); + case 'raw time hence': return format_interval(-$time_diff, $interval); + case 'time hence': return t('%time hence', array('%time' => format_interval(-$time_diff, $interval))); + case 'raw time span': return ($time_diff < 0 ? '-' : '') . format_interval(abs($time_diff), $interval); + case 'inverse time span': return ($time_diff > 0 ? '-' : '') . format_interval(abs($time_diff), $interval); + case 'time span': return t(($time_diff < 0 ? '%time hence' : '%time ago'), array('%time' => format_interval(abs($time_diff), $interval))); + } } diff --git a/sites/all/modules/contrib/fields/date/date_context/date_context.info b/sites/all/modules/contrib/fields/date/date_context/date_context.info index a69a599..97cb8c9 100644 --- a/sites/all/modules/contrib/fields/date/date_context/date_context.info +++ b/sites/all/modules/contrib/fields/date/date_context/date_context.info @@ -8,9 +8,9 @@ dependencies[] = context files[] = date_context.module files[] = plugins/date_context_date_condition.inc -; Information added by drupal.org packaging script on 2012-08-13 -version = "7.x-2.6" +; Information added by Drupal.org packaging script on 2017-04-07 +version = "7.x-2.10" core = "7.x" project = "date" -datestamp = "1344850024" +datestamp = "1491562090" diff --git a/sites/all/modules/contrib/fields/date/date_context/date_context.module b/sites/all/modules/contrib/fields/date/date_context/date_context.module index 44e975a..9b568e1 100644 --- a/sites/all/modules/contrib/fields/date/date_context/date_context.module +++ b/sites/all/modules/contrib/fields/date/date_context/date_context.module @@ -1,5 +1,8 @@ execute() @@ -8,8 +11,6 @@ * Cache the date processing, perhaps cache the formatted, timezone-adjusted * date strings for each entity (would have to be cached differently for each * timezone, based on the tz_handling method for the date). - * - * Add an option to set/not set the context on forms vs views. */ /** @@ -22,7 +23,7 @@ function date_context_context_node_condition_alter($node, $op) { } /** - * Implements hook_context_plugins() + * Implements hook_context_plugins(). */ function date_context_context_plugins() { $plugins = array(); @@ -38,7 +39,7 @@ function date_context_context_plugins() { } /** - * Implements hook_context_registry() + * Implements hook_context_registry(). */ function date_context_context_registry() { return array( @@ -51,4 +52,3 @@ function date_context_context_registry() { ), ); } - diff --git a/sites/all/modules/contrib/fields/date/date_context/plugins/date_context_date_condition.inc b/sites/all/modules/contrib/fields/date/date_context/plugins/date_context_date_condition.inc index 733fb5a..97c18b4 100644 --- a/sites/all/modules/contrib/fields/date/date_context/plugins/date_context_date_condition.inc +++ b/sites/all/modules/contrib/fields/date/date_context/plugins/date_context_date_condition.inc @@ -1,10 +1,20 @@ $field) { @@ -15,10 +25,13 @@ class date_context_date_condition extends context_condition_node { return $values; } - function options_form($context) { + /** + * {@inheritdoc} + */ + public function options_form($context) { $defaults = $this->fetch_from_context($context, 'options'); $options = array( - '<' => t('Is less than'), + '<' => t('Is less than'), '<=' => t('Is less than or equal to'), '>=' => t('Is greater than or equal to'), '>' => t('Is greater than'), @@ -27,6 +40,8 @@ class date_context_date_condition extends context_condition_node { 'empty' => t('Is empty'), 'not empty' => t('Is not Empty'), ); + $dependency_options = array('<', '<=', '>', '>=', '=', '!='); + $form['operation'] = array( '#title' => t('Operation'), '#type' => 'select', @@ -41,12 +56,15 @@ class date_context_date_condition extends context_condition_node { '#description' => t("The value the field should contain to meet the condition. This can either be an absolute date in ISO format (YYYY-MM-DDTHH:MM:SS) or a relative string like '12AM today'. Examples: 2011-12-31T00:00:00, now, now +1 day, 12AM today, Monday next week. More examples of relative date formats in the PHP documentation.", array('@relative_format' => 'http://www.php.net/manual/en/datetime.formats.relative.php')), '#default_value' => isset($defaults['value']) ? $defaults['value'] : '', '#process' => array('ctools_dependent_process'), - '#dependency' => array(':input[name="conditions[plugins][date_context_date_condition][options][operation]"]' => array('<', '<=', '>', '>=', '=', '!=')), + '#dependency' => array('edit-conditions-plugins-date-context-date-condition-options-operation' => $dependency_options), ); return $form; } - function execute($entity, $op) { + /** + * {@inheritdoc} + */ + public function execute($entity, $op) { if (in_array($op, array('view', 'form'))) { foreach ($this->get_contexts() as $context) { $options = $this->fetch_from_context($context, 'options'); @@ -91,32 +109,37 @@ class date_context_date_condition extends context_condition_node { str_replace('now', 'today', $options['value']); $date = date_create($options['value'], date_default_timezone_object()); $compdate = $date->format(DATE_FORMAT_DATETIME); - switch($options['operation']) { + switch ($options['operation']) { case '=': if ($date2 >= $compdate && $date1 <= $compdate) { $this->condition_met($context, $field_name); } break; + case '>': if ($date1 > $compdate) { $this->condition_met($context, $field_name); } break; + case '>=': if ($date1 >= $compdate) { $this->condition_met($context, $field_name); } break; + case '<': if ($date2 < $compdate) { $this->condition_met($context, $field_name); } break; + case '<=': if ($date2 <= $compdate) { $this->condition_met($context, $field_name); } break; + case '!=': if ($date1 < $compdate || $date2 > $compdate) { $this->condition_met($context, $field_name); @@ -130,3 +153,4 @@ class date_context_date_condition extends context_condition_node { } } } +// @codingStandardsIgnoreEnd diff --git a/sites/all/modules/contrib/fields/date/date_elements.inc b/sites/all/modules/contrib/fields/date/date_elements.inc index 20620ed..656d499 100644 --- a/sites/all/modules/contrib/fields/date/date_elements.inc +++ b/sites/all/modules/contrib/fields/date/date_elements.inc @@ -40,7 +40,6 @@ * * - In the field's submission processing, the new date values, which are in * the local timezone, are converted back to their UTC values and stored. - * */ function date_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $base) { @@ -75,9 +74,9 @@ function date_field_widget_form(&$form, &$form_state, $field, $instance, $langco // The repeating values will be re-generated when the repeat widget form is validated. // At this point we can't tell if this form element is going to be hidden by #access, and we're going to // lose all but the first value by doing this, so store the original values in case we need to replace them later. - if (!empty($field['settings']['repeat'])) { + if (!empty($field['settings']['repeat']) && module_exists('date_repeat_field')) { if ($delta == 0) { - $form['#after_build'] = array('date_repeat_after_build'); + $form['#after_build'][] = 'date_repeat_after_build'; $form_state['storage']['repeat_fields'][$field_name] = array_merge($form['#parents'], array($field_name)); $form_state['storage']['date_items'][$field_name][$langcode] = $items; } @@ -87,7 +86,7 @@ function date_field_widget_form(&$form, &$form_state, $field, $instance, $langco } module_load_include('inc', 'date_api', 'date_api_elements'); - $timezone = date_get_timezone($field['settings']['tz_handling'], isset($items[0]['timezone']) ? $items[0]['timezone'] : date_default_timezone()); + $timezone = date_get_timezone($field['settings']['tz_handling'], isset($items[$delta]['timezone']) ? $items[$delta]['timezone'] : date_default_timezone()); // TODO see if there's a way to keep the timezone element from ever being // nested as array('timezone' => 'timezone' => value)). After struggling @@ -122,7 +121,13 @@ function date_field_widget_form(&$form, &$form_state, $field, $instance, $langco '#weight' => $instance['widget']['weight'] + 1, '#attributes' => array('class' => array('date-no-float')), '#date_label_position' => $instance['widget']['settings']['label_position'], - ); + ); + } + + // Make changes if instance is set to be rendered as a regular field. + if (!empty($instance['widget']['settings']['no_fieldset'])) { + $element['#title'] = check_plain($instance['label']); + $element['#theme_wrappers'] = ($field['cardinality'] == 1) ? array('date_form_element') : array(); } return $element; @@ -148,6 +153,7 @@ function date_local_date($item, $timezone, $field, $instance, $part = 'value') { // @TODO Figure out how to replace date_fuzzy_datetime() function. // Special case for ISO dates to create a valid date object for formatting. // Is this still needed? + // @codingStandardsIgnoreStart /* if ($field['type'] == DATE_ISO) { $value = date_fuzzy_datetime($value); @@ -157,6 +163,7 @@ function date_local_date($item, $timezone, $field, $instance, $part = 'value') { $value = date_convert($value, $field['type'], DATE_DATETIME, $db_timezone); } */ + // @codingStandardsIgnoreEnd $date = new DateObject($value, date_get_timezone_db($field['settings']['tz_handling'])); $date->limitGranularity($field['settings']['granularity']); @@ -193,8 +200,7 @@ function date_default_value($field, $instance, $langcode) { } /** - * Helper function for the date default value callback to set - * either 'value' or 'value2' to its default value. + * Helper function for the date default value callback to set either 'value' or 'value2' to its default value. */ function date_default_value_part($item, $field, $instance, $langcode, $part = 'value') { $timezone = date_get_timezone($field['settings']['tz_handling']); @@ -241,7 +247,6 @@ function date_default_value_part($item, $field, $instance, $langcode, $part = 'v * Process an individual date element. */ function date_combo_element_process($element, &$form_state, $form) { - if (date_hidden_element($element)) { // A hidden value for a new entity that had its end date set to blank // will not get processed later to populate the end date, so set it here. @@ -296,6 +301,7 @@ function date_combo_element_process($element, &$form_state, $form) { // Blank out the end date for optional end dates that match the start date, // except when this is a new node that has default values that should be honored. if (!$date_is_default && $field['settings']['todate'] != 'required' + && is_array($element['#default_value']) && !empty($element['#default_value'][$to_field]) && $element['#default_value'][$to_field] == $element['#default_value'][$from_field]) { unset($element['#default_value'][$to_field]); @@ -320,7 +326,7 @@ function date_combo_element_process($element, &$form_state, $form) { '#field' => $field, '#instance' => $instance, '#weight' => $instance['widget']['weight'], - '#required' => ($instance['required'] && $delta == 0) ? 1 : 0, + '#required' => ($element['#required'] && $delta == 0) ? 1 : 0, '#default_value' => isset($element['#default_value'][$from_field]) ? $element['#default_value'][$from_field] : '', '#delta' => $delta, '#date_timezone' => $element['#date_timezone'], @@ -329,9 +335,13 @@ function date_combo_element_process($element, &$form_state, $form) { '#date_increment' => $instance['widget']['settings']['increment'], '#date_year_range' => $instance['widget']['settings']['year_range'], '#date_label_position' => $instance['widget']['settings']['label_position'], - ); + ); - $description = !empty($instance['description']) ? t($instance['description']) : ''; + // Date repeat is a multiple value field. So the description is removed from + // the single element earlier. Let's get it back. + if (isset($element['show_repeat_settings']) && !empty($element['value']['#instance']['description'])) { + $element['#description'] = $element['value']['#instance']['description']; + } // Give this element the right type, using a Date API // or a Date Popup element type. @@ -346,11 +356,13 @@ function date_combo_element_process($element, &$form_state, $form) { $element['#attached']['js'][] = drupal_get_path('module', 'date') . '/date.js'; $element[$from_field]['#ajax'] = !empty($element['#ajax']) ? $element['#ajax'] : FALSE; break; + case 'date_popup': $element[$from_field]['#type'] = 'date_popup'; $element[$from_field]['#theme_wrappers'] = array('date_popup'); $element[$from_field]['#ajax'] = !empty($element['#ajax']) ? $element['#ajax'] : FALSE; break; + default: $element[$from_field]['#type'] = 'date_text'; $element[$from_field]['#theme_wrappers'] = array('date_text'); @@ -363,8 +375,8 @@ function date_combo_element_process($element, &$form_state, $form) { // is the 'Start' and which is the 'End' . if (!empty($field['settings']['todate'])) { - $element[$from_field]['#title'] = ''; $element[$to_field] = $element[$from_field]; + $element[$from_field]['#title_display'] = 'none'; $element[$to_field]['#title'] = t('to:'); $element[$from_field]['#wrapper_attributes']['class'][] = 'start-date-wrapper'; $element[$to_field]['#wrapper_attributes']['class'][] = 'end-date-wrapper'; @@ -374,18 +386,17 @@ function date_combo_element_process($element, &$form_state, $form) { $element[$to_field]['#prefix'] = ''; // Users with JS enabled will never see initially blank values for the end // date (see Drupal.date.EndDateHandler()), so hide the message for them. - $description .= ' ' . t("Empty 'End date' values will use the 'Start date' values.") . ''; - $element['#fieldset_description'] = $description; + $element['#description'] .= ' ' . t("Empty 'End date' values will use the 'Start date' values.") . ''; if ($field['settings']['todate'] == 'optional') { $element[$to_field]['#states'] = array( 'visible' => array( - 'input[name="' . $show_id . '"]' => array('checked' => TRUE), - )); + 'input[name="' . $show_id . '"]' => array( + 'checked' => TRUE, + ), + ), + ); } } - else { - $element[$from_field]['#description'] = $description; - } // Create label for error messages that make sense in multiple values // and when the title field is left blank. @@ -400,19 +411,30 @@ function date_combo_element_process($element, &$form_state, $form) { $element[$to_field]['#date_title'] = t('@field_name End date', array('@field_name' => $instance['label'])); } else { - $element[$from_field]['#date_title'] = $instance['label']; + $element[$from_field]['#date_title'] = t('@field_name', array('@field_name' => $instance['label'])); + } + + // Make changes if instance is set to be rendered as a regular field. + if (!empty($instance['widget']['settings']['no_fieldset'])) { + unset($element[$from_field]['#description']); + if (!empty($field['settings']['todate']) && isset($element['#description'])) { + $element['#description'] .= ' ' . t("Empty 'End date' values will use the 'Start date' values.") . ''; + } } $context = array( - 'field' => $field, - 'instance' => $instance, - 'form' => $form, + 'field' => $field, + 'instance' => $instance, + 'form' => $form, ); drupal_alter('date_combo_process', $element, $form_state, $context); return $element; } +/** + * Empty a date element. + */ function date_element_empty($element, &$form_state) { $item = array(); $item['value'] = NULL; @@ -427,6 +449,7 @@ function date_element_empty($element, &$form_state) { /** * Validate and update a combo element. + * * Don't try this if there were errors before reaching this point. */ function date_combo_validate($element, &$form_state) { @@ -443,16 +466,26 @@ function date_combo_validate($element, &$form_state) { $delta = $element['#delta']; $langcode = $element['#language']; + // Related issue: https://drupal.org/node/2279831. + if (!is_array($element['#field_parents'])) { + $element['#field_parents'] = array(); + } $form_values = drupal_array_get_nested_value($form_state['values'], $element['#field_parents']); $form_input = drupal_array_get_nested_value($form_state['input'], $element['#field_parents']); + // Programmatically calling drupal_submit_form() does not always add the date + // combo to $form_state['input']. + if (empty($form_input[$field_name]) && !empty($form_values[$field_name])) { + form_set_value($element, $element['#date_items'], $form_state); + return; + } // If the whole field is empty and that's OK, stop now. if (empty($form_input[$field_name]) && !$element['#required']) { return; } - $item = $form_values[$field_name][$langcode][$delta]; - $posted = $form_input[$field_name][$langcode][$delta]; + $item = drupal_array_get_nested_value($form_state['values'], $element['#parents']); + $posted = drupal_array_get_nested_value($form_state['input'], $element['#parents']); $field = field_widget_field($element, $form_state); $instance = field_widget_instance($element, $form_state); @@ -518,11 +551,7 @@ function date_combo_validate($element, &$form_state) { return; } } - // Don't look for further errors if errors are already flagged - // because otherwise we'll show errors on the nested elements - // more than once. - elseif (!form_get_errors()) { - + else { $timezone = !empty($item[$tz_field]) ? $item[$tz_field] : $element['#date_timezone']; $timezone_db = date_get_timezone_db($field['settings']['tz_handling']); $element[$from_field]['#date_timezone'] = $timezone; @@ -597,7 +626,10 @@ function date_combo_validate($element, &$form_state) { } } } - if (!empty($errors)) { + // Don't show further errors if errors are already flagged + // because otherwise we'll show errors on the nested elements + // more than once. + if (!form_get_errors() && !empty($errors)) { if ($field['cardinality']) { form_error($element, t('There are errors in @field_name value #@delta:', array('@field_name' => $instance['label'], '@delta' => $delta + 1)) . theme('item_list', array('items' => $errors))); } diff --git a/sites/all/modules/contrib/fields/date/date_migrate/date_migrate.info b/sites/all/modules/contrib/fields/date/date_migrate/date_migrate.info index c0d536f..41e416a 100644 --- a/sites/all/modules/contrib/fields/date/date_migrate/date_migrate.info +++ b/sites/all/modules/contrib/fields/date/date_migrate/date_migrate.info @@ -1,16 +1,12 @@ name = Date Migration -description = Provides support for importing into date fields with the Migrate module. +description = Obsolete data migration module. Disable if no other modules depend on it. core = 7.x package = Date/Time +hidden = TRUE -dependencies[] = migrate -dependencies[] = date -files[] = date.migrate.inc -files[] = date_migrate.test - -; Information added by drupal.org packaging script on 2012-08-13 -version = "7.x-2.6" +; Information added by Drupal.org packaging script on 2017-04-07 +version = "7.x-2.10" core = "7.x" project = "date" -datestamp = "1344850024" +datestamp = "1491562090" diff --git a/sites/all/modules/contrib/fields/date/date_migrate/date_migrate.module b/sites/all/modules/contrib/fields/date/date_migrate/date_migrate.module index accdcf8..158791a 100644 --- a/sites/all/modules/contrib/fields/date/date_migrate/date_migrate.module +++ b/sites/all/modules/contrib/fields/date/date_migrate/date_migrate.module @@ -2,15 +2,5 @@ /** * @file - * Migration integration for Date Migrate. + * Obsolete migration integration for Date - now in Date itself. */ - -/** - * Implements hook_migrate_api(). - */ -function date_migrate_migrate_api() { - $api = array( - 'api' => 2, - ); - return $api; -} diff --git a/sites/all/modules/contrib/fields/date/date_migrate/date_migrate_example/date_migrate_example.info b/sites/all/modules/contrib/fields/date/date_migrate/date_migrate_example/date_migrate_example.info index b54a3f7..ba07220 100644 --- a/sites/all/modules/contrib/fields/date/date_migrate/date_migrate_example/date_migrate_example.info +++ b/sites/all/modules/contrib/fields/date/date_migrate/date_migrate_example/date_migrate_example.info @@ -2,7 +2,6 @@ core = "7.x" dependencies[] = "date" dependencies[] = "date_repeat" dependencies[] = "date_repeat_field" -dependencies[] = "date_migrate" dependencies[] = "features" dependencies[] = "migrate" description = "Examples of migrating with the Date module" @@ -21,9 +20,9 @@ package = "Features" project = "date_migrate_example" version = "7.x-2.0" -; Information added by drupal.org packaging script on 2012-08-13 -version = "7.x-2.6" +; Information added by Drupal.org packaging script on 2017-04-07 +version = "7.x-2.10" core = "7.x" project = "date" -datestamp = "1344850024" +datestamp = "1491562090" diff --git a/sites/all/modules/contrib/fields/date/date_migrate/date_migrate_example/date_migrate_example.install b/sites/all/modules/contrib/fields/date/date_migrate/date_migrate_example/date_migrate_example.install index 7a35cec..d202bd0 100644 --- a/sites/all/modules/contrib/fields/date/date_migrate/date_migrate_example/date_migrate_example.install +++ b/sites/all/modules/contrib/fields/date/date_migrate/date_migrate_example/date_migrate_example.install @@ -12,3 +12,12 @@ function date_migrate_example_disable() { Migration::deregisterMigration('DateExample'); } +/** + * Implements hook_uninstall(). + */ +function date_migrate_example_uninstall() { + node_type_delete('date_migrate_example'); + variable_del('node_preview_date_migrate_example'); + node_types_rebuild(); + menu_rebuild(); +} diff --git a/sites/all/modules/contrib/fields/date/date_migrate/date_migrate_example/date_migrate_example.migrate.inc b/sites/all/modules/contrib/fields/date/date_migrate/date_migrate_example/date_migrate_example.migrate.inc index 491c27f..216e1c5 100644 --- a/sites/all/modules/contrib/fields/date/date_migrate/date_migrate_example/date_migrate_example.migrate.inc +++ b/sites/all/modules/contrib/fields/date/date_migrate/date_migrate_example/date_migrate_example.migrate.inc @@ -47,8 +47,8 @@ class DateExampleMigration extends XMLMigration { $xml_folder = drupal_get_path('module', 'date_migrate_example'); $items_url = $xml_folder . '/date_migrate_example.xml'; $item_xpath = '/source_data/item'; - $item_ID_xpath = 'id'; - $items_class = new MigrateItemsXML($items_url, $item_xpath, $item_ID_xpath); + $item_id_xpath = 'id'; + $items_class = new MigrateItemsXML($items_url, $item_xpath, $item_id_xpath); $this->source = new MigrateSourceMultiItems($items_class, $fields); $this->destination = new MigrateDestinationNode('date_migrate_example'); @@ -66,28 +66,34 @@ class DateExampleMigration extends XMLMigration { // For date ranges, we add the "end" value in prepareRow() below. $this->addFieldMapping('field_date_range', 'date_range_from'); + $this->addFieldMapping('field_date_range:to', 'date_range_to'); // RRULEs on repeat fields are also done in prepareRow(). $this->addFieldMapping('field_date_repeat', 'date_repeat'); + $this->addFieldMapping('field_date_repeat:rrule', 'date_repeat_rrule'); $this->addFieldMapping('field_datestamp', 'datestamp') ->xpath('datestamp'); $this->addFieldMapping('field_datestamp_range', 'datestamp_range_from'); + $this->addFieldMapping('field_datestamp_range:to', 'datestamp_range_to'); // You can specify a timezone to be applied to all values going into the - // field (Tokyo is UTC+9, no DST) - $arguments = DateMigrateFieldHandler::arguments('Asia/Tokyo'); + // field (Tokyo is UTC+9, no DST). $this->addFieldMapping('field_datetime', 'datetime') - ->xpath('datetime') - ->arguments($arguments); + ->xpath('datetime'); + $this->addFieldMapping('field_datetime:timezone') + ->defaultValue('Asia/Tokyo'); // You can also get the timezone from the source data - it can be different // for each instance of the field. Like To and RRULE values, it is added // in prepareRow(). $this->addFieldMapping('field_datetime_range', 'datetime_range_from'); + $this->addFieldMapping('field_datetime_range:to', 'datetime_range_to'); + $this->addFieldMapping('field_datetime_range:timezone', 'datetime_range_timezone'); // Unmapped destination fields. - $this->addUnmigratedDestinations(array('is_new', 'status', 'promote', 'revision', 'language', 'sticky', 'created', 'changed', 'revision_uid')); + $this->addUnmigratedDestinations(array('is_new', 'status', 'promote', + 'revision', 'language', 'sticky', 'created', 'changed', 'revision_uid')); } /** @@ -101,30 +107,25 @@ class DateExampleMigration extends XMLMigration { // The date range field can have multiple values. $current_row->date_range_from = array(); foreach ($current_row->xml->date_range as $range) { - $date_data = array( - 'from' => (string) $range->from[0], - 'to' => (string) $range->to[0], - ); - $current_row->date_range_from[] = drupal_json_encode($date_data); + $current_row->date_range_from[] = (string) $range->from[0]; + $current_row->date_range_to[] = (string) $range->to[0]; } - $date_data = array( - 'from' => (string) $current_row->xml->datestamp_range->from[0], - 'to' => (string) $current_row->xml->datestamp_range->to[0], - ); - $current_row->datestamp_range_from = drupal_json_encode($date_data); + $current_row->datestamp_range_from + = (string) $current_row->xml->datestamp_range->from[0]; + $current_row->datestamp_range_to + = (string) $current_row->xml->datestamp_range->to[0]; - $date_data = array( - 'from' => (string) $current_row->xml->datetime_range->from[0], - 'to' => (string) $current_row->xml->datetime_range->to[0], - 'timezone' => (string) $current_row->xml->datetime_range->timezone[0], - ); - $current_row->datetime_range_from = drupal_json_encode($date_data); + $current_row->datetime_range_from + = (string) $current_row->xml->datetime_range->from[0]; + $current_row->datetime_range_to + = (string) $current_row->xml->datetime_range->to[0]; + $current_row->datetime_range_timezone + = (string) $current_row->xml->datetime_range->timezone[0]; - $date_data = array( - 'from' => (string) $current_row->xml->date_repeat->date[0], - 'rrule' => (string) $current_row->xml->date_repeat->rule[0], - ); - $current_row->date_repeat = drupal_json_encode($date_data); + $current_row->date_repeat + = (string) $current_row->xml->date_repeat->date[0]; + $current_row->date_repeat_rrule + = (string) $current_row->xml->date_repeat->rule[0]; } } diff --git a/sites/all/modules/contrib/fields/date/date_migrate/date_migrate_example/date_migrate_example.module b/sites/all/modules/contrib/fields/date/date_migrate/date_migrate_example/date_migrate_example.module index 993bd11..2ed7a61 100644 --- a/sites/all/modules/contrib/fields/date/date_migrate/date_migrate_example/date_migrate_example.module +++ b/sites/all/modules/contrib/fields/date/date_migrate/date_migrate_example/date_migrate_example.module @@ -13,6 +13,9 @@ include_once 'date_migrate_example.features.inc'; function date_migrate_example_migrate_api() { $api = array( 'api' => 2, + 'migrations' => array( + 'DateExample' => array('class_name' => 'DateExampleMigration') + ), ); return $api; } diff --git a/sites/all/modules/contrib/fields/date/date_popup/README.txt b/sites/all/modules/contrib/fields/date/date_popup/README.txt index 149810f..7a66628 100644 --- a/sites/all/modules/contrib/fields/date/date_popup/README.txt +++ b/sites/all/modules/contrib/fields/date/date_popup/README.txt @@ -18,7 +18,7 @@ Timepicker ================================================================================ There are three ways to let users select time in the Date Popup widgets. -You can choose between them by going to admin/config/content/date_popup. +You can choose between them by going to admin/config/date/date_popup. The options are: @@ -99,10 +99,10 @@ Example: $form['date'] = array( '#type' => 'date_popup', - '#default_value' => '2007-01-01 10:30:00, + '#default_value' => '2007-01-01 10:30:00', '#date_type' => DATE_DATETIME, '#date_timezone' => date_default_timezone(), - '#date_format' => 'm/d/Y - H:i', + '#date_format' => 'm-d-Y H:i', '#date_increment' => 1, '#date_year_range' => '-3:+3', ); diff --git a/sites/all/modules/contrib/fields/date/date_popup/date_popup.info b/sites/all/modules/contrib/fields/date/date_popup/date_popup.info index 767aaca..41945a6 100644 --- a/sites/all/modules/contrib/fields/date/date_popup/date_popup.info +++ b/sites/all/modules/contrib/fields/date/date_popup/date_popup.info @@ -7,9 +7,9 @@ configure = admin/config/date/date_popup stylesheets[all][] = themes/datepicker.1.7.css -; Information added by drupal.org packaging script on 2012-08-13 -version = "7.x-2.6" +; Information added by Drupal.org packaging script on 2017-04-07 +version = "7.x-2.10" core = "7.x" project = "date" -datestamp = "1344850024" +datestamp = "1491562090" diff --git a/sites/all/modules/contrib/fields/date/date_popup/date_popup.install b/sites/all/modules/contrib/fields/date/date_popup/date_popup.install index 790a514..e13a4f5 100644 --- a/sites/all/modules/contrib/fields/date/date_popup/date_popup.install +++ b/sites/all/modules/contrib/fields/date/date_popup/date_popup.install @@ -5,6 +5,7 @@ * Install, update and uninstall functions for the Date Popup module. */ +// @codingStandardsIgnoreStart /** * Implements hook_install(). */ @@ -17,6 +18,7 @@ function date_popup_install() { function date_popup_uninstall() { } +// @codingStandardsIgnoreEnd /** * Implements hook_enable(). diff --git a/sites/all/modules/contrib/fields/date/date_popup/date_popup.js b/sites/all/modules/contrib/fields/date/date_popup/date_popup.js index 1847f84..f38dcb5 100644 --- a/sites/all/modules/contrib/fields/date/date_popup/date_popup.js +++ b/sites/all/modules/contrib/fields/date/date_popup/date_popup.js @@ -1,62 +1,73 @@ /** * Attaches the calendar behavior to all required fields */ -(function ($) { -Drupal.behaviors.date_popup = { - attach: function (context) { - for (var id in Drupal.settings.datePopup) { - $('#'+ id).bind('focus', Drupal.settings.datePopup[id], function(e) { - if (!$(this).hasClass('date-popup-init')) { - var datePopup = e.data; - // Explicitely filter the methods we accept. - switch (datePopup.func) { - case 'datepicker': - $(this) - .datepicker(datePopup.settings) - .addClass('date-popup-init') - $(this).click(function(){ - $(this).focus(); +(function($) { + function makeFocusHandler(e) { + if (!$(this).hasClass('date-popup-init')) { + var datePopup = e.data; + // Explicitely filter the methods we accept. + switch (datePopup.func) { + case 'datepicker': + $(this) + .datepicker(datePopup.settings) + .addClass('date-popup-init'); + $(this).click(function(){ + $(this).focus(); + }); + if (datePopup.settings.syncEndDate) { + $('.start-date-wrapper').each(function(){ + var start_date_wrapper = this; + $(this).find('input:eq(0)').change(function(){ + $(start_date_wrapper).next('.end-date-wrapper').find('input:eq(0)').val($(this).val()); + }); }); - break; + } + break; - case 'timeEntry': - $(this) - .timeEntry(datePopup.settings) - .addClass('date-popup-init') - $(this).click(function(){ - $(this).focus(); - }); - break; - case 'timepicker': - // Translate the PHP date format into the style the timepicker uses. - datePopup.settings.timeFormat = datePopup.settings.timeFormat - // 12-hour, leading zero, - .replace('h', 'hh') - // 12-hour, no leading zero. - .replace('g', 'h') - // 24-hour, leading zero. - .replace('H', 'HH') - // 24-hour, no leading zero. - .replace('G', 'H') - // AM/PM. - .replace('A', 'p') - // Minutes with leading zero. - .replace('i', 'mm') - // Seconds with leading zero. - .replace('s', 'ss'); + case 'timeEntry': + $(this) + .timeEntry(datePopup.settings) + .addClass('date-popup-init'); + $(this).click(function(){ + $(this).focus(); + }); + break; - datePopup.settings.startTime = new Date(datePopup.settings.startTime); - $(this) - .timepicker(datePopup.settings) - .addClass('date-popup-init'); - $(this).click(function(){ - $(this).focus(); - }); - break; - } + case 'timepicker': + // Translate the PHP date format into the style the timepicker uses. + datePopup.settings.timeFormat = datePopup.settings.timeFormat + // 12-hour, leading zero, + .replace('h', 'hh') + // 12-hour, no leading zero. + .replace('g', 'h') + // 24-hour, leading zero. + .replace('H', 'HH') + // 24-hour, no leading zero. + .replace('G', 'H') + // AM/PM. + .replace('A', 'p') + // Minutes with leading zero. + .replace('i', 'mm') + // Seconds with leading zero. + .replace('s', 'ss'); + + datePopup.settings.startTime = new Date(datePopup.settings.startTime); + $(this) + .timepicker(datePopup.settings) + .addClass('date-popup-init'); + $(this).click(function(){ + $(this).focus(); + }); + break; } - }); + } } - } -}; + + Drupal.behaviors.date_popup = { + attach: function (context) { + for (var id in Drupal.settings.datePopup) { + $('#'+ id).bind('focus', Drupal.settings.datePopup[id], makeFocusHandler); + } + } + }; })(jQuery); diff --git a/sites/all/modules/contrib/fields/date/date_popup/date_popup.module b/sites/all/modules/contrib/fields/date/date_popup/date_popup.module index ca292ef..a03132e 100644 --- a/sites/all/modules/contrib/fields/date/date_popup/date_popup.module +++ b/sites/all/modules/contrib/fields/date/date_popup/date_popup.module @@ -16,7 +16,6 @@ * If no time elements are included in the format string, only the date * textfield will be created. If no date elements are included in the format * string, only the time textfield, will be created. - * */ /** @@ -44,7 +43,7 @@ function date_popup_add() { /** * Get the location of the Willington Vega timepicker library. * - * @return + * @return string * The location of the library, or FALSE if the library isn't installed. */ function date_popup_get_wvega_path() { @@ -87,16 +86,18 @@ function date_popup_library() { $path . '/jquery.timeentry.pack.js' => array(), ), 'css' => array( - $path . '/themes/jquery.timeentry.css' => array('preprocess' => FALSE), + $path . '/themes/jquery.timeentry.css' => array(), ), ); return $libraries; } /** - * Create a unique CSS id name and output a single inline JS block for - * each startup function to call and settings array to pass it. This - * used to create a unique CSS class for each unique combination of + * Create a unique CSS id name and output a single inline JS block. + * + * For each startup function to call and settings array to pass it. + * + * This used to create a unique CSS class for each unique combination of * function and settings, but using classes requires a DOM traversal * and is much slower than an id lookup. The new approach returns to * requiring a duplicate copy of the settings/code for every element @@ -104,17 +105,20 @@ function date_popup_library() { * putting the ids for each unique function/settings combo into * Drupal.settings and searching for each listed id. * - * @param $pfx + * @param string $id * The CSS class prefix to search the DOM for. * TODO : unused ? - * @param $func - * The jQuery function to invoke on each DOM element containing the - * returned CSS class. - * @param $settings + * + * @param string $func + * The jQuery function to invoke on each DOM element + * containing the returned CSS class. + * + * @param array $settings * The settings array to pass to the jQuery function. + * * @returns - * The CSS id to assign to the element that should have - * $func($settings) invoked on it. + * The CSS id to assign to the element that should have $func($settings) + * invoked on it. */ function date_popup_js_settings_id($id, $func, $settings) { static $js_added = FALSE; @@ -123,14 +127,15 @@ function date_popup_js_settings_id($id, $func, $settings) { // Make sure popup date selector grid is in correct year. if (!empty($settings['yearRange'])) { $parts = explode(':', $settings['yearRange']); - // Set the default date to 0 or the lowest bound if the date ranges do not include the current year - // Necessary for the datepicker to render and select dates correctly - $defaultDate = ($parts[0] > 0 || 0 > $parts[1]) ? $parts[0] : 0; - $settings += array('defaultDate' => (string) $defaultDate . 'y'); + // Set the default date to 0 or the lowest bound if + // the date ranges do not include the current year. + // Necessary for the datepicker to render and select dates correctly. + $default_date = ($parts[0] > 0 || 0 > $parts[1]) ? $parts[0] : 0; + $settings += array('defaultDate' => (string) $default_date . 'y'); } if (!$js_added) { - drupal_add_js(drupal_get_path('module', 'date_popup') .'/date_popup.js'); + drupal_add_js(drupal_get_path('module', 'date_popup') . '/date_popup.js'); $js_added = TRUE; } @@ -140,29 +145,35 @@ function date_popup_js_settings_id($id, $func, $settings) { $id_count[$id] = 0; } -// It looks like we need the additional id_count for this to -// work correctly when there are multiple values. -// $return_id = "$id-$func-popup"; - $return_id = "$id-$func-popup-". $id_count[$id]++; + // It looks like we need the additional id_count for this to + // work correctly when there are multiple values. + // $return_id = "$id-$func-popup"; + $return_id = "$id-$func-popup-" . $id_count[$id]++; $js_settings['datePopup'][$return_id] = array( 'func' => $func, - 'settings' => $settings + 'settings' => $settings, ); drupal_add_js($js_settings, 'setting'); return $return_id; } +/** + * Date popup theme handler. + */ function date_popup_theme() { return array( - 'date_popup' => array('render element' => 'element'), - ); + 'date_popup' => array( + 'render element' => 'element', + ), + ); } /** * Implements hook_element_info(). * * Set the #type to date_popup and fill the element #default_value with - * a date adjusted to the proper local timezone in datetime format (YYYY-MM-DD HH:MM:SS). + * a date adjusted to the proper local timezone in datetime format + * (YYYY-MM-DD HH:MM:SS). * * The element will create two textfields, one for the date and one for the * time. The date textfield will include a jQuery popup calendar date picker, @@ -218,20 +229,32 @@ function date_popup_element_info() { return $type; } +/** + * Date popup date granularity. + */ function date_popup_date_granularity($element) { $granularity = date_format_order($element['#date_format']); return array_intersect($granularity, array('month', 'day', 'year')); } +/** + * Date popup time granularity. + */ function date_popup_time_granularity($element) { $granularity = date_format_order($element['#date_format']); return array_intersect($granularity, array('hour', 'minute', 'second')); } +/** + * Date popup date format. + */ function date_popup_date_format($element) { return (date_limit_format($element['#date_format'], date_popup_date_granularity($element))); } +/** + * Date popup time format. + */ function date_popup_time_format($element) { return date_popup_format_to_popup_time(date_limit_format($element['#date_format'], date_popup_time_granularity($element)), $element['#timepicker']); } @@ -239,6 +262,7 @@ function date_popup_time_format($element) { /** * Element value callback for date_popup element. */ +// @codingStandardsIgnoreStart function date_popup_element_value_callback($element, $input = FALSE, &$form_state) { $granularity = date_format_order($element['#date_format']); $has_time = date_has_time($granularity); @@ -266,9 +290,11 @@ function date_popup_element_value_callback($element, $input = FALSE, &$form_stat return $return; } +// @codingStandardsIgnoreEnd /** * Javascript popup element processing. + * * Add popup attributes to $element. */ function date_popup_element_process($element, &$form_state, $form) { @@ -284,7 +310,9 @@ function date_popup_element_process($element, &$form_state, $form) { if (!empty($element['#ajax'])) { $element['#ajax'] += array( - 'trigger_as' => array('name' =>$element['#name']), + 'trigger_as' => array( + 'name' => $element['#name'], + ), 'event' => 'change', ); } @@ -292,6 +320,18 @@ function date_popup_element_process($element, &$form_state, $form) { $element['date'] = date_popup_process_date_part($element); $element['time'] = date_popup_process_time_part($element); + // Make changes if instance is set to be rendered as a regular field. + if (!empty($element['#instance']['widget']['settings']['no_fieldset']) && $element['#field']['cardinality'] == 1) { + if (!empty($element['date']) && empty($element['time'])) { + $element['date']['#title'] = check_plain($element['#instance']['label']); + $element['date']['#required'] = $element['#required']; + } + elseif (empty($element['date']) && !empty($element['time'])) { + $element['time']['#title'] = check_plain($element['#instance']['label']); + $element['time']['#required'] = $element['#required']; + } + } + if (isset($element['#element_validate'])) { array_push($element['#element_validate'], 'date_popup_validate'); } @@ -300,7 +340,7 @@ function date_popup_element_process($element, &$form_state, $form) { } $context = array( - 'form' => $form, + 'form' => $form, ); drupal_alter('date_popup_process', $element, $form_state, $context); @@ -313,13 +353,22 @@ function date_popup_element_process($element, &$form_state, $form) { function date_popup_process_date_part(&$element) { $granularity = date_format_order($element['#date_format']); $date_granularity = date_popup_date_granularity($element); - if (empty($date_granularity)) return array(); + if (empty($date_granularity)) { + return array(); + } // The datepicker can't handle zero or negative values like 0:+1 // even though the Date API can handle them, so rework the value // we pass to the datepicker to use defaults it can accept (such as +0:+1) // date_range_string() adds the necessary +/- signs to the range string. $this_year = date_format(date_now(), 'Y'); + // When used as a Views exposed filter widget, $element['#value'] contains an array instead an string. + // Fill the 'date' string in this case. + $mock = NULL; + $callback_values = date_popup_element_value_callback($element, FALSE, $mock); + if (!isset($element['#value']['date']) && isset($callback_values['date'])) { + $element['#value']['date'] = $callback_values['date']; + } $date = ''; if (!empty($element['#value']['date'])) { $date = new DateObject($element['#value']['date'], $element['#date_timezone'], date_popup_date_format($element)); @@ -336,36 +385,49 @@ function date_popup_process_date_part(&$element) { 'closeAtTop' => FALSE, 'speed' => 'immediate', 'firstDay' => intval(variable_get('date_first_day', 0)), - //'buttonImage' => base_path() . drupal_get_path('module', 'date_api') ."/images/calendar.png", - //'buttonImageOnly' => TRUE, + // 'buttonImage' => base_path() + // . drupal_get_path('module', 'date_api') ."/images/calendar.png", + // 'buttonImageOnly' => TRUE, 'dateFormat' => date_popup_format_to_popup(date_popup_date_format($element), 'datepicker'), 'yearRange' => $year_range, // Custom setting, will be expanded in Drupal.behaviors.date_popup() 'fromTo' => isset($fromto), ); + if (!empty($element['#instance'])) { + $settings['syncEndDate'] = $element['#instance']['settings']['default_value2'] == 'sync'; + } + // Create a unique id for each set of custom settings. $id = date_popup_js_settings_id($element['#id'], 'datepicker', $settings); - // Manually build this element and set the value - this will prevent corrupting - // the parent value + // Manually build this element and set the value - + // this will prevent corrupting the parent value. $parents = array_merge($element['#parents'], array('date')); $sub_element = array( '#type' => 'textfield', - '#title' => $element['#date_label_position'] == 'above' ? theme('date_part_label_date', array('part_type' => 'date', 'element' => $element)) : '', - '#default_value' => $element['#value']['date'], + '#title' => theme('date_part_label_date', array('part_type' => 'date', 'element' => $element)), + '#title_display' => $element['#date_label_position'] == 'above' ? 'before' : 'invisible', + '#default_value' => date_format_date($date, 'custom', date_popup_date_format($element)), '#id' => $id, '#input' => FALSE, '#size' => !empty($element['#size']) ? $element['#size'] : 20, '#maxlength' => !empty($element['#maxlength']) ? $element['#maxlength'] : 30, '#attributes' => $element['#attributes'], '#parents' => $parents, - '#name' => array_shift($parents) . '['. implode('][', $parents) .']', + '#name' => array_shift($parents) . '[' . implode('][', $parents) . ']', '#ajax' => !empty($element['#ajax']) ? $element['#ajax'] : FALSE, ); $sub_element['#value'] = $sub_element['#default_value']; - // TODO, figure out exactly when we want this description. In many places it is not desired. - $sub_element['#description'] = ' '. t('E.g., @date', array('@date' => date_format_date(date_example_date(), 'custom', date_popup_date_format($element)))); + // TODO, figure out exactly when we want this description. + // In many places it is not desired. + $sub_element['#description'] = ' ' . t('E.g., @date', array( + '@date' => date_format_date( + date_example_date(), + 'custom', + date_popup_date_format($element) + ), + )); return $sub_element; } @@ -376,7 +438,17 @@ function date_popup_process_date_part(&$element) { function date_popup_process_time_part(&$element) { $granularity = date_format_order($element['#date_format']); $has_time = date_has_time($granularity); - if (empty($has_time)) return array(); + if (empty($has_time)) { + return array(); + } + + // When used as a Views exposed filter widget, $element['#value'] contains an array instead an string. + // Fill the 'time' string in this case. + $mock = NULL; + $callback_values = date_popup_element_value_callback($element, FALSE, $mock); + if (!isset($element['#value']['time']) && isset($callback_values['time'])) { + $element['#value']['time'] = $callback_values['time']; + } switch ($element['#timepicker']) { case 'default': @@ -384,10 +456,14 @@ function date_popup_process_time_part(&$element) { $settings = array( 'show24Hours' => strpos($element['#date_format'], 'H') !== FALSE ? TRUE : FALSE, 'showSeconds' => (in_array('second', $granularity) ? TRUE : FALSE), - 'timeSteps' => array(1, intval($element['#date_increment']), (in_array('second', $granularity) ? $element['#date_increment'] : 0)), + 'timeSteps' => array( + 1, + intval($element['#date_increment']), + (in_array('second', $granularity) ? $element['#date_increment'] : 0), + ), 'spinnerImage' => '', 'fromTo' => isset($fromto), - ); + ); if (strpos($element['#date_format'], 'a') !== FALSE) { // Then we are using lowercase am/pm. $settings['ampmNames'] = array('am', 'pm'); @@ -396,14 +472,17 @@ function date_popup_process_time_part(&$element) { $settings['ampmPrefix'] = ' '; } break; + case 'wvega': $func = 'timepicker'; - $time_granularity = array_intersect($granularity, array('hour', 'minute', 'second')); + $grans = array('hour', 'minute', 'second'); + $time_granularity = array_intersect($granularity, $grans); $format = date_popup_format_to_popup_time(date_limit_format($element['#date_format'], $time_granularity), 'wvega'); + $default_value = isset($element['#default_value']) ? $element['#default_value'] : ''; // The first value in the dropdown list should be the same as the element // default_value, but it needs to be in JS format (i.e. milliseconds since // the epoch). - $start_time = new DateObject($element['#default_value'], $element['#date_timezone'], DATE_FORMAT_DATETIME); + $start_time = new DateObject($default_value, $element['#date_timezone'], DATE_FORMAT_DATETIME); date_increment_round($start_time, $element['#date_increment']); $start_time = $start_time->format(DATE_FORMAT_UNIX) * 1000; $settings = array( @@ -413,6 +492,7 @@ function date_popup_process_time_part(&$element) { 'scrollbar' => TRUE, ); break; + default: $func = ''; $settings = array(); @@ -422,28 +502,35 @@ function date_popup_process_time_part(&$element) { // Create a unique id for each set of custom settings. $id = date_popup_js_settings_id($element['#id'], $func, $settings); - // Manually build this element and set the value - this will prevent corrupting - // the parent value + // Manually build this element and set the value - + // this will prevent corrupting the parent value. $parents = array_merge($element['#parents'], array('time')); $sub_element = array( '#type' => 'textfield', - '#title' => $element['#date_label_position'] == 'above' ? theme('date_part_label_time', array('part_type' => 'time', 'element' => $element)) : '', + '#title' => theme('date_part_label_time', array('part_type' => 'time', 'element' => $element)), + '#title_display' => $element['#date_label_position'] == 'above' ? 'before' : 'invisible', '#default_value' => $element['#value']['time'], '#id' => $id, '#size' => 15, '#maxlength' => 10, '#attributes' => $element['#attributes'], '#parents' => $parents, - '#name' => array_shift($parents) . '['. implode('][', $parents) .']', + '#name' => array_shift($parents) . '[' . implode('][', $parents) . ']', '#ajax' => !empty($element['#ajax']) ? $element['#ajax'] : FALSE, ); $sub_element['#value'] = $sub_element['#default_value']; - // TODO, figure out exactly when we want this description. In many places it is not desired. + // TODO, figure out exactly when we want this description. + // In many places it is not desired. $example_date = date_now(); date_increment_round($example_date, $element['#date_increment']); - $sub_element['#description'] = t('E.g., @date', array('@date' => date_format_date($example_date, 'custom', date_popup_time_format($element)))); + $sub_element['#description'] = t('E.g., @date', array( + '@date' => date_format_date( + $example_date, + 'custom', + date_popup_time_format($element) + ))); return ($sub_element); } @@ -454,7 +541,6 @@ function date_popup_process_time_part(&$element) { * When used as a Views widget, the validation step always gets triggered, * even with no form submission. Before form submission $element['#value'] * contains a string, after submission it contains an array. - * */ function date_popup_validate($element, &$form_state) { @@ -471,6 +557,11 @@ function date_popup_validate($element, &$form_state) { $input_exists = NULL; $input = drupal_array_get_nested_value($form_state['values'], $element['#parents'], $input_exists); + // If the date is a string, it is not considered valid and can cause problems + // later on, so just exit out now. + if (is_string($input)) { + return; + } drupal_alter('date_popup_pre_validate', $element, $form_state, $input); @@ -479,16 +570,22 @@ function date_popup_validate($element, &$form_state) { $time_granularity = date_popup_time_granularity($element); $has_time = date_has_time($granularity); - $label = !empty($element['#date_title']) ? $element['#date_title'] : (!empty($element['#title']) ? $element['#title'] : ''); - $label = t($label); - + // @codingStandardsIgnoreStart + $label = ''; + if (!empty($element['#date_title'])) { + $label = t($element['#date_title']); + } + elseif (!empty($element['#title'])) { + $label = t($element['#title']); + } + // @codingStandardsIgnoreEnd $date = date_popup_input_date($element, $input); // If the date has errors, display them. // If something was input but there is no date, the date is invalid. // If the field is empty and required, set error message and return. $error_field = implode('][', $element['#parents']); - if (empty($date) || !empty($date->errors)) { + if ((empty($element['#value']['date']) && empty($element['#value']['time'])) || !empty($date->errors)) { if (is_object($date) && !empty($date->errors)) { $message = t('The value input for field %field is invalid:', array('%field' => $label)); $message .= '
    ' . implode('
    ', $date->errors); @@ -515,13 +612,15 @@ function date_popup_validate($element, &$form_state) { /** * Helper function for extracting a date value out of user input. * - * @param autocomplete + * @param bool $auto_complete * Should we add a time value to complete the date if there is no time? * Useful anytime the time value is optional. */ function date_popup_input_date($element, $input, $auto_complete = FALSE) { if (empty($input) || !is_array($input) || !array_key_exists('date', $input) || empty($input['date'])) { - return NULL; + //check if there is no time associated in the input variable. This is the exception scenario where the user has entered only time and not date. + if(empty($input['time'])) + return NULL; } date_popup_add(); $granularity = date_format_order($element['#date_format']); @@ -530,9 +629,14 @@ function date_popup_input_date($element, $input, $auto_complete = FALSE) { $format = date_popup_date_format($element); $format .= $has_time ? ' ' . date_popup_time_format($element) : ''; - $datetime = $input['date']; - $datetime .= $has_time ? ' ' . $input['time'] : ''; + //check if date is empty, if yes, then leave it blank. + $datetime = !empty($input['date']) ? trim($input['date']) : ''; + $datetime .= $has_time ? ' ' . trim($input['time']) : ''; $date = new DateObject($datetime, $element['#date_timezone'], $format); + //if the variable is time only then set TimeOnly to TRUE + if(empty($input['date']) && !empty($input['time']) ){ + $date->timeOnly = 'TRUE'; + } if (is_object($date)) { $date->limitGranularity($granularity); if ($date->validGranularity($granularity, $flexible)) { @@ -550,7 +654,7 @@ function date_popup_time_formats($with_seconds = FALSE) { return array( 'H:i:s', 'h:i:sA', - ); + ); } /** @@ -559,8 +663,17 @@ function date_popup_time_formats($with_seconds = FALSE) { * TODO Remove any formats not supported by the widget, if any. */ function date_popup_formats() { - $formats = str_replace('i', 'i:s', array_keys(system_get_date_formats('short'))); + // Load short date formats. + $formats = system_get_date_formats('short'); + + // Load custom date formats. + if ($formats_custom = system_get_date_formats('custom')) { + $formats = array_merge($formats, $formats_custom); + } + + $formats = str_replace('i', 'i:s', array_keys($formats)); $formats = drupal_map_assoc($formats); + return $formats; } @@ -568,7 +681,8 @@ function date_popup_formats() { * Recreate a date format string so it has the values popup expects. * * @param string $format - * a normal date format string, like Y-m-d + * A normal date format string, like Y-m-d + * * @return string * A format string in popup format, like YMD-, for the * earlier 'calendar' version, or m/d/Y for the later 'datepicker' @@ -586,15 +700,34 @@ function date_popup_format_to_popup($format) { * Recreate a time format string so it has the values popup expects. * * @param string $format - * a normal time format string, like h:i (a) + * A normal time format string, like h:i (a) + * * @return string - * a format string that the popup can accept like h:i a + * A format string that the popup can accept like h:i a */ function date_popup_format_to_popup_time($format, $timepicker = NULL) { if (empty($format)) { $format = 'H:i'; } - $format = str_replace(array('/', '-', ' .', ',', 'F', 'M', 'l', 'z', 'w', 'W', 'd', 'j', 'm', 'n', 'y', 'Y'), '', $format); + $symbols = array( + '/', + '-', + ' .', + ',', + 'F', + 'M', + 'l', + 'z', + 'w', + 'W', + 'd', + 'j', + 'm', + 'n', + 'y', + 'Y', + ); + $format = str_replace($symbols, '', $format); $format = strtr($format, date_popup_timepicker_format_replacements($timepicker)); return $format; } @@ -603,9 +736,10 @@ function date_popup_format_to_popup_time($format, $timepicker = NULL) { * Reconstruct popup format string into normal format string. * * @param string $format - * a string in popup format, like YMD- + * A string in popup format, like YMD- + * * @return string - * a normal date format string, like Y-m-d + * A normal date format string, like Y-m-d */ function date_popup_popup_to_format($format) { $replace = array_flip(date_popup_datepicker_format_replacements()); @@ -619,22 +753,21 @@ function date_popup_popup_to_format($format) { * This function returns a map of format replacements required to change any * input format into one that the given timepicker can support. * - * @param $timepicker + * @param string $timepicker * The time entry plugin being used: either 'wvega' or 'default'. - * @return + * + * @return array * A map of replacements. */ function date_popup_timepicker_format_replacements($timepicker = 'default') { switch ($timepicker) { case 'wvega': - return array( - 'a' => 'A', // The wvega timepicker only supports uppercase AM/PM. - ); + // The wvega timepicker only supports uppercase AM/PM. + return array('a' => 'A'); + default: - return array( - 'G' => 'H', // The default timeEntry plugin requires leading zeros. - 'g' => 'h', - ); + // The default timeEntry plugin requires leading zeros. + return array('G' => 'H', 'g' => 'h'); } } @@ -643,16 +776,16 @@ function date_popup_timepicker_format_replacements($timepicker = 'default') { */ function date_popup_datepicker_format_replacements() { return array( - 'd' => 'dd', - 'j' => 'd', - 'l' => 'DD', - 'D' => 'D', - 'm' => 'mm', - 'n' => 'm', - 'F' => 'MM', - 'M' => 'M', - 'Y' => 'yy', - 'y' => 'y', + 'd' => 'dd', + 'j' => 'd', + 'l' => 'DD', + 'D' => 'D', + 'm' => 'mm', + 'n' => 'm', + 'F' => 'MM', + 'M' => 'M', + 'Y' => 'yy', + 'y' => 'y', ); } @@ -665,16 +798,26 @@ function theme_date_popup($vars) { $element = $vars['element']; $attributes = !empty($element['#wrapper_attributes']) ? $element['#wrapper_attributes'] : array('class' => array()); $attributes['class'][] = 'container-inline-date'; - // If there is no description, the floating date elements need some extra padding below them. + // If there is no description, the floating date + // elements need some extra padding below them. $wrapper_attributes = array('class' => array('date-padding')); if (empty($element['date']['#description'])) { $wrapper_attributes['class'][] = 'clearfix'; } - // Add an wrapper to mimic the way a single value field works, for ease in using #states. + // Add an wrapper to mimic the way a single value field works, + // for ease in using #states. if (isset($element['#children'])) { - $element['#children'] = '
    ' . $element['#children'] . '
    '; + $element['#children'] = '
    ' . $element['#children'] . '
    '; } - return '
    ' . theme('form_element', $element) . '
    '; + return '
    ' . theme('form_element', $element) . '
    '; +} + +/** + * Implements hook_date_field_instance_settings_form_alter(). + */ +function date_popup_date_field_instance_settings_form_alter(&$form, $context) { + // Add an extra option to sync the end date with the start date. + $form['default_value2']['#options']['sync'] = t('Sync with start date'); } /** @@ -705,8 +848,8 @@ function date_popup_settings() { '#type' => 'select', '#options' => array( 'default' => t('Use default jQuery timepicker'), - 'wvega' => t('Use dropdown timepicker'), - 'none' => t('Manual time entry, no jQuery timepicker') + 'wvega' => t('Use dropdown timepicker'), + 'none' => t('Manual time entry, no jQuery timepicker'), ), '#title' => t('Timepicker'), '#default_value' => variable_get('date_popup_timepicker', $preferred_timepicker), @@ -732,7 +875,7 @@ function date_popup_settings() { } EOM; - $form['#suffix'] = t('

    The Date Popup calendar includes some css for IE6 that breaks css validation. Since IE 6 is now superceded by IE 7, 8, and 9, the special css for IE 6 has been removed from the regular css used by the Date Popup. If you find you need that css after all, you can add it back in your theme. Look at the way the Garland theme adds special IE-only css in in its page.tpl.php file. The css you need is:

    ') .'
    ' . $css .'
    '; + $form['#suffix'] = t('

    The Date Popup calendar includes some css for IE6 that breaks css validation. Since IE 6 is now superceded by IE 7, 8, and 9, the special css for IE 6 has been removed from the regular css used by the Date Popup. If you find you need that css after all, you can add it back in your theme. Look at the way the Garland theme adds special IE-only css in in its page.tpl.php file. The css you need is:

    ') . '
    ' . $css . '
    '; return system_settings_form($form); } diff --git a/sites/all/modules/contrib/fields/date/date_popup/jquery.timeentry.pack.js b/sites/all/modules/contrib/fields/date/date_popup/jquery.timeentry.pack.js index e26c64b..658e7e4 100644 --- a/sites/all/modules/contrib/fields/date/date_popup/jquery.timeentry.pack.js +++ b/sites/all/modules/contrib/fields/date/date_popup/jquery.timeentry.pack.js @@ -1,7 +1,8 @@ /* http://keith-wood.name/timeEntry.html - Time entry for jQuery v1.4.8. + Time entry for jQuery v1.5.2. Written by Keith Wood (kbwood{at}iinet.com.au) June 2007. - Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and - MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses. + Licensed under the MIT (https://github.com/jquery/jquery/blob/master/MIT-LICENSE.txt) license. Please attribute the author if you use it. */ -eval(function(p,a,c,k,e,r){e=function(c){return(c35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('(r($){r 1n(){p.Y=[];p.26=[];p.26[\'\']={1c:B,1d:\':\',1o:\'\',Z:[\'3J\',\'3K\'],2C:[\'3L\',\'3M 2D\',\'3N 2D\',\'3O\',\'3P\']};p.1B={2E:\'\',1C:B,27:[1,1,1],2F:0,2G:L,2H:v,2I:v,2J:v,2K:\'3Q.3R\',1p:[20,20,8],2L:\'\',1D:[40,40,16],2M:B,28:[3S,3T],2N:v,2O:v};$.1q(p.1B,p.26[\'\'])}7 m=\'o\';$.1q(1n.2P,{1e:\'3U\',3V:r(a){1E(p.1B,a||{});u p},2Q:r(b,c){7 d=$(b);q(d.2R(p.1e)){u}7 e={};e.1r=$.1q({},c);e.E=0;e.13=0;e.14=0;e.x=0;e.w=$(b);$.y(b,m,e);7 f=p.t(e,\'2K\');7 g=p.t(e,\'3W\');7 h=p.t(e,\'1p\');7 i=p.t(e,\'2E\');7 j=(!f?v:$(\'<15 1F="3X" 2S="3Y: 3Z-41; \'+\'29: 2T(\\\'\'+f+\'\\\') 0 0 2U-2V; \'+\'2a: \'+h[0]+\'O; 2W: \'+h[1]+\'O;\'+($.K.2X&&$.K.42<\'1.9\'?\' 2Y-U: \'+h[0]+\'O; 2Y-43: \'+(h[1]-18)+\'O;\':\'\')+\'">\'));d.44(\'<15 1F="45">\').2Z(i?\'<15 1F="47">\'+i+\'\':\'\').2Z(j||\'\');d.49(p.1e).17(\'2b.o\',p.2c).17(\'4a.o\',p.30).17(\'4b.o\',p.31).17(\'4c.o\',p.32).17(\'4d.o\',p.33);q($.K.2X){d.17(\'w.o\',r(a){$.o.1f(e)})}q($.K.2d){d.17(\'4e.o\',r(a){1G(r(){$.o.1f(e)},1)})}q(p.t(e,\'2G\')&&$.1H.2e){d.2e(p.34)}q(j){j.3a(p.2f).2g(p.1I).4f(p.3b).2h(p.1I).3c(p.2i)}},4g:r(a){p.2j(a,B)},4h:r(a){p.2j(a,L)},2j:r(b,c){7 d=$.y(b,m);q(!d){u}b.3d=c;q(b.2k&&b.2k.1J.1g()==\'15\'){$.o.1K(d,b.2k,(c?5:-1))}$.o.Y=$.3e($.o.Y,r(a){u(a==b?v:a)});q(c){$.o.Y.4i(b)}},1s:r(a){u $.3f(a,p.Y)>-1},4j:r(a,b,c){7 d=$.y(a,m);q(d){q(1h b==\'1L\'){7 e=b;b={};b[e]=c}7 f=p.1i(d);1E(d.1r,b||{});q(f){p.19(d,F J(0,0,0,f[0],f[1],f[2]))}}$.y(a,m,d)},4k:r(b){$w=$(b);q(!$w.2R(p.1e)){u}$w.4l(p.1e).4m(\'.o\');q($.1H.2e){$w.4n()}p.Y=$.3e(p.Y,r(a){u(a==b?v:a)});$w.4o().4p($w);$.4q(b,m)},4r:r(a,b){7 c=$.y(a,m);q(c){p.19(c,b?(1h b==\'4s\'?F J(b.2l()):b):v)}},3g:r(a){7 b=$.y(a,m);7 c=(b?p.1i(b):v);u(!c?v:F J(0,0,0,c[0],c[1],c[2]))},4t:r(a){7 b=$.y(a,m);7 c=(b?p.1i(b):v);u(!c?0:(c[0]*4u+c[1]*2m+c[2])*3h)},2c:r(a){7 b=(a.1J&&a.1J.1g()==\'w\'?a:p);q($.o.P==b||$.o.1s(b)){$.o.1M=B;u}7 c=$.y(b,m);$.o.1M=L;$.o.P=b;$.o.1j=v;7 d=$.o.t(c,\'2N\');1E(c.1r,(d?d.1N(b,[b]):{}));$.y(b,m,c);$.o.1f(c);1G(r(){$.o.1a(c)},10)},30:r(a){$.o.1j=$.o.P;$.o.P=v},31:r(b){7 c=b.1k;7 d=$.y(c,m);q(!$.o.1M){7 e=$.o.t(d,\'1d\').G+2;d.x=0;q(c.3i!=v){1O(7 f=0;f<=I.1l(1,d.V,d.D);f++){7 g=(f!=d.D?(f*e)+2:(d.D*e)+$.o.t(d,\'1o\').G+$.o.t(d,\'Z\')[0].G);d.x=f;q(c.3i=48){u L}7 b=$.y(a.1k,m);2r(a.2q){A 9:u(a.4B?$.o.W(b,-1,L):$.o.W(b,+1,L));A 35:q(a.3m){$.o.1R(b,\'\')}N{b.x=I.1l(1,b.V,b.D);$.o.R(b,0)}C;A 36:q(a.3m){$.o.19(b)}N{b.x=0;$.o.R(b,0)}C;A 37:$.o.W(b,-1,B);C;A 38:$.o.R(b,+1);C;A 39:$.o.W(b,+1,B);C;A 40:$.o.R(b,-1);C;A 46:$.o.1R(b,\'\');C}u B},33:r(a){7 b=4C.4D(a.3n==4E?a.2q:a.3n);q(b<\' \'){u L}7 c=$.y(a.1k,m);$.o.3o(c,b);u B},34:r(a,b){q($.o.1s(a.1k)){u}b=($.K.3p?-b/I.1S(b):($.K.2s?b/I.1S(b):b));7 c=$.y(a.1k,m);c.w.2b();q(!c.w.T()){$.o.1f(c)}$.o.R(c,b);a.4F()},3b:r(b){7 c=$.o.1b(b);7 d=$.y($.o.1m(c),m);7 e=$.o.t(d,\'2L\');q(e){d.1T=L;7 f=$(c).1Q();7 g=v;$(c).3q().2t(r(){7 a=$(p);q(a.1v(\'1w\')==\'4G\'||a.1v(\'1w\')==\'3r\'){g=a.1Q()}u!g});7 h=$.o.t(d,\'1p\');7 i=$.o.t(d,\'1D\');$(\'<3s 1F="4H" 2S="1w: 3r; U: \'+(f.U-(i[0]-h[0])/2-(g?g.U:0))+\'O; 1x: \'+(f.1x-(i[1]-h[1])/2-(g?g.1x:0))+\'O; 2a: \'+i[0]+\'O; 2W: \'+i[1]+\'O; 29: 4I 2T(\'+e+\') 2U-2V 2u 2u; z-4J: 10;">\').3a($.o.2f).2g($.o.1I).2h($.o.3t).3c($.o.2i).4K(c)}},1m:r(a){u $(a).4L(\'.\'+$.o.1e)[0]},2i:r(a){7 b=$.o.1b(a);7 c=$.y($.o.1m(b),m);b.4M=$.o.t(c,\'2C\')[$.o.2v(c,a)]},2f:r(a){7 b=$.o.1b(a);7 c=$.o.1m(b);q($.o.1s(c)){u}q(c==$.o.1j){$.o.P=c;$.o.1j=v}7 d=$.y(c,m);$.o.2c(c);7 e=$.o.2v(d,a);$.o.1K(d,b,e);$.o.2w(d,e);$.o.X=v;$.o.1U=L;7 f=$.o.t(d,\'28\');q(e>=3&&f[0]){$.o.X=1G(r(){$.o.2x(d,e)},f[0]);$(b).3u(\'2h\',$.o.2y).3u(\'2g\',$.o.2y)}},2w:r(a,b){q(!a.w.T()){$.o.1f(a)}2r(b){A 0:p.19(a);C;A 1:p.W(a,-1,B);C;A 2:p.W(a,+1,B);C;A 3:p.R(a,+1);C;A 4:p.R(a,-1);C}},2x:r(a,b){q(!$.o.X){u}$.o.P=$.o.1j;p.2w(a,b);p.X=1G(r(){$.o.2x(a,b)},p.t(a,\'28\')[1])},2y:r(a){4N($.o.X);$.o.X=v},3t:r(a){$.o.X=v;7 b=$.o.1b(a);7 c=$.o.1m(b);7 d=$.y(c,m);$(b).4O();d.1T=B},1I:r(a){$.o.X=v;7 b=$.o.1b(a);7 c=$.o.1m(b);7 d=$.y(c,m);q(!$.o.1s(c)){$.o.1K(d,b,-1)}q($.o.1U){$.o.P=$.o.1j}q($.o.P&&$.o.1U){$.o.1a(d)}$.o.1U=B},1b:r(a){u a.1k||a.3j},2v:r(a,b){7 c=p.1b(b);7 d=($.K.3p||$.K.2s?$.o.3v(c):$(c).1Q());7 e=($.K.2s?$.o.3w(c):[1t.2n.1u||1t.3x.1u,1t.2n.1V||1t.3x.1V]);7 f=p.t(a,\'2M\');7 g=(f?3y:b.3k+e[0]-d.U-($.K.2d?2:0));7 h=b.4P+e[1]-d.1x-($.K.2d?2:0);7 i=p.t(a,(a.1T?\'1D\':\'1p\'));7 j=(f?3y:i[0]-1-g);7 k=i[1]-1-h;q(i[2]>0&&I.1S(g-j)<=i[2]&&I.1S(h-k)<=i[2]){u 0}7 l=I.3z(g,h,j,k);u(l==g?1:(l==j?2:(l==h?3:4)))},1K:r(a,b,c){$(b).1v(\'29-1w\',\'-\'+((c+1)*p.t(a,(a.1T?\'1D\':\'1p\'))[0])+\'O 2u\')},3v:r(a){7 b=1W=0;q(a.3A){b=a.2o;1W=a.3B;2z(a=a.3A){7 c=b;b+=a.2o;q(b<0){b=c}1W+=a.3B}}u{U:b,1x:1W}},3w:r(a){7 b=B;$(a).3q().2t(r(){b|=$(p).1v(\'1w\')==\'4Q\'});q(b){u[0,0]}7 c=a.1u;7 d=a.1V;2z(a=a.4R){c+=a.1u||0;d+=a.1V||0}u[c,d]},t:r(a,b){u(a.1r[b]!=v?a.1r[b]:$.o.1B[b])},1f:r(a){7 b=p.1i(a);7 c=p.t(a,\'1C\');q(b){a.E=b[0];a.13=b[1];a.14=b[2]}N{7 d=p.1y(a);a.E=d[0];a.13=d[1];a.14=(c?d[2]:0)}a.V=(c?2:-1);a.D=(p.t(a,\'1c\')?-1:(c?3:2));a.1X=\'\';a.x=I.1l(0,I.3z(I.1l(1,a.V,a.D),p.t(a,\'2F\')));q(a.w.T()!=\'\'){p.2A(a)}},1i:r(a,b){b=b||a.w.T();7 c=p.t(a,\'1d\');7 d=b.4S(c);q(c==\'\'&&b!=\'\'){d[0]=b.1z(0,2);d[1]=b.1z(2,4);d[2]=b.1z(4,6)}7 e=p.t(a,\'Z\');7 f=p.t(a,\'1c\');q(d.G>=2){7 g=!f&&(b.3C(e[0])>-1);7 h=!f&&(b.3C(e[1])>-1);7 i=Q(d[0],10);i=(2B(i)?0:i);i=((g||h)&&i==12?0:i)+(h?12:0);7 j=Q(d[1],10);j=(2B(j)?0:j);7 k=(d.G>=3?Q(d[2],10):0);k=(2B(k)||!p.t(a,\'1C\')?0:k);u p.1y(a,[i,j,k])}u v},1y:r(a,b){7 c=(b!=v);q(!c){7 d=p.1A(a,p.t(a,\'2H\'))||F J();b=[d.1Y(),d.1Z(),d.21()]}7 e=B;7 f=p.t(a,\'27\');1O(7 i=0;i1){b[i]=I.4T(b[i]/f[i])*f[i];e=L}}u b},2A:r(a){7 b=p.t(a,\'1c\');7 c=p.t(a,\'1d\');7 d=(p.22(b?a.E:((a.E+11)%12)+1)+c+p.22(a.13)+(p.t(a,\'1C\')?c+p.22(a.14):\'\')+(b?\'\':p.t(a,\'1o\')+p.t(a,\'Z\')[(a.E<12?0:1)]));p.1R(a,d);p.1a(a)},1a:r(a){7 b=a.w[0];q(a.w.4U(\':4V\')||$.o.P!=b){u}7 c=p.t(a,\'1d\');7 d=c.G+2;7 e=(a.x!=a.D?(a.x*d):(a.D*d)-c.G+p.t(a,\'1o\').G);7 f=e+(a.x!=a.D?2:p.t(a,\'Z\')[0].G);q(b.3D){b.3D(e,f)}N q(b.1P){7 g=b.1P();g.4W(\'2p\',e);g.3l(\'2p\',f-a.w.T().G);g.4X()}q(!b.3d){b.2b()}},22:r(a){u(a<10?\'0\':\'\')+a},1R:r(a,b){q(b!=a.w.T()){a.w.T(b).4Y(\'4Z\')}},W:r(a,b,c){7 d=(a.w.T()==\'\'||a.x==(b==-1?0:I.1l(1,a.V,a.D)));q(!d){a.x+=b}p.1a(a);a.1X=\'\';$.y(a.w[0],m,a);u(d&&c)},R:r(a,b){q(a.w.T()==\'\'){b=0}7 c=p.t(a,\'27\');p.19(a,F J(0,0,0,a.E+(a.x==0?b*c[0]:0)+(a.x==a.D?b*12:0),a.13+(a.x==1?b*c[1]:0),a.14+(a.x==a.V?b*c[2]:0)))},19:r(a,b){b=p.1A(a,b);7 c=p.1y(a,b?[b.1Y(),b.1Z(),b.21()]:v);b=F J(0,0,0,c[0],c[1],c[2]);7 b=p.25(b);7 d=p.25(p.1A(a,p.t(a,\'2I\')));7 e=p.25(p.1A(a,p.t(a,\'2J\')));b=(d&&be?e:b));7 f=p.t(a,\'2O\');q(f){b=f.1N(a.w[0],[p.3g(a.w[0]),b,d,e])}a.E=b.1Y();a.13=b.1Z();a.14=b.21();p.2A(a);$.y(a.w[0],m,a)},25:r(a){q(!a){u v}a.50(51);a.52(0);a.53(0);u a},1A:r(i,j){7 k=r(a){7 b=F J();b.54(b.2l()+a*3h);u b};7 l=r(a){7 b=$.o.1i(i,a);7 c=F J();7 d=(b?b[0]:c.1Y());7 e=(b?b[1]:c.1Z());7 f=(b?b[2]:c.21());q(!b){7 g=/([+-]?[0-9]+)\\s*(s|S|m|M|h|H)?/g;7 h=g.3E(a);2z(h){2r(h[2]||\'s\'){A\'s\':A\'S\':f+=Q(h[1],10);C;A\'m\':A\'M\':e+=Q(h[1],10);C;A\'h\':A\'H\':d+=Q(h[1],10);C}h=g.3E(a)}}c=F J(0,0,10,d,e,f,0);q(/^!/.55(a)){q(c.3F()>10){c=F J(0,0,10,23,59,59)}N q(c.3F()<10){c=F J(0,0,10,0,0,0)}}u c};u(j?(1h j==\'1L\'?l(j):(1h j==\'56\'?k(j):j)):v)},3o:r(a,b){q(b==p.t(a,\'1d\')){p.W(a,+1,B)}N q(b>=\'0\'&&b<=\'9\'){7 c=Q(b,10);7 d=Q(a.1X+b,10);7 e=p.t(a,\'1c\');7 f=(a.x!=0?a.E:(e?(d<24?d:c):(d>=1&&d<=12?d:(c>0?c:a.E))%12+(a.E>=12?12:0)));7 g=(a.x!=1?a.13:(d<2m?d:c));7 h=(a.x!=a.V?a.14:(d<2m?d:c));7 i=p.1y(a,[f,g,h]);p.19(a,F J(0,0,0,i[0],i[1],i[2]));a.1X=b}N q(!p.t(a,\'1c\')){b=b.1g();7 j=p.t(a,\'Z\');q((b==j[0].1z(0,1).1g()&&a.E>=12)||(b==j[1].1z(0,1).1g()&&a.E<12)){7 k=a.x;a.x=a.D;p.R(a,+1);a.x=k;p.1a(a)}}}});r 1E(a,b){$.1q(a,b);1O(7 c 57 b){q(b[c]==v){a[c]=v}}u a}7 n=[\'58\',\'2l\',\'5a\'];$.1H.o=r(c){7 d=5b.2P.5c.5d(5e,1);q(1h c==\'1L\'&&$.3f(c,n)>-1){u $.o[\'3G\'+c+\'1n\'].1N($.o,[p[0]].3H(d))}u p.2t(r(){7 a=p.1J.1g();q(a==\'w\'){q(1h c==\'1L\'){$.o[\'3G\'+c+\'1n\'].1N($.o,[p].3H(d))}N{7 b=($.1H.3I?$(p).3I():{});$.o.2Q(p,$.1q(b,c))}}})};$.o=F 1n()})(5f);',62,326,'|||||||var|||||||||||||||||timeEntry|this|if|function||_get|return|null|input|_field|data||case|false|break|_ampmField|_selectedHour|new|length||Math|Date|browser|true||else|px|_lastInput|parseInt|_adjustField||val|left|_secondField|_changeField|_timer|_disabledInputs|ampmNames||||_selectedMinute|_selectedSecond|span||bind||_setTime|_showField|_getSpinnerTarget|show24Hours|separator|markerClassName|_parseTime|toLowerCase|typeof|_extractTime|_blurredInput|target|max|_getInput|TimeEntry|ampmPrefix|spinnerSize|extend|options|_isDisabledTimeEntry|document|scrollLeft|css|position|top|_constrainTime|substring|_determineTime|_defaults|showSeconds|spinnerBigSize|extendRemove|class|setTimeout|fn|_endSpinner|nodeName|_changeSpinner|string|_focussed|apply|for|createTextRange|offset|_setValue|abs|_expanded|_handlingSpinner|scrollTop|curTop|_lastChr|getHours|getMinutes||getSeconds|_formatNumber|||_normaliseTime|regional|timeSteps|spinnerRepeat|background|width|focus|_doFocus|msie|mousewheel|_handleSpinner|mouseup|mouseout|_describeSpinner|_enableDisable|nextSibling|getTime|60|documentElement|offsetLeft|character|keyCode|switch|safari|each|0px|_getSpinnerRegion|_actionSpinner|_repeatSpinner|_releaseSpinner|while|_showTime|isNaN|spinnerTexts|field|appendText|initialField|useMouseWheel|defaultTime|minTime|maxTime|spinnerImage|spinnerBigImage|spinnerIncDecOnly|beforeShow|beforeSetTime|prototype|_connectTimeEntry|hasClass|style|url|no|repeat|height|mozilla|padding|after|_doBlur|_doClick|_doKeyDown|_doKeyPress|_doMouseWheel||||||mousedown|_expandSpinner|mousemove|disabled|map|inArray|_getTimeTimeEntry|1000|selectionStart|srcElement|clientX|moveEnd|ctrlKey|charCode|_handleKeyPress|opera|parents|absolute|div|_endExpand|one|_findPos|_findScroll|body|99|min|offsetParent|offsetTop|indexOf|setSelectionRange|exec|getDate|_|concat|metadata|AM|PM|Now|Previous|Next|Increment|Decrement|spinnerDefault|png|500|250|hasTimeEntry|setDefaults|spinnerText|timeEntry_control|display|inline||block|version|bottom|wrap|timeEntry_wrap||timeEntry_append||addClass|blur|click|keydown|keypress|paste|mouseover|_enableTimeEntry|_disableTimeEntry|push|_changeTimeEntry|_destroyTimeEntry|removeClass|unbind|unmousewheel|parent|replaceWith|removeData|_setTimeTimeEntry|object|_getOffsetTimeEntry|3600|thin|medium|thick|border|collapse|boundingWidth|shiftKey|String|fromCharCode|undefined|preventDefault|relative|timeEntry_expand|transparent|index|insertAfter|siblings|title|clearTimeout|remove|clientY|fixed|parentNode|split|round|is|hidden|moveStart|select|trigger|change|setFullYear|1900|setMonth|setDate|setTime|test|number|in|getOffset||isDisabled|Array|slice|call|arguments|jQuery'.split('|'),0,{})) \ No newline at end of file +/* jshint ignore:start */ +(function($){function TimeEntry(){this._disabledInputs=[];this.regional=[];this.regional['']={show24Hours:false,separator:':',ampmPrefix:'',ampmNames:['AM','PM'],spinnerTexts:['Now','Previous field','Next field','Increment','Decrement']};this._defaults={appendText:'',showSeconds:false,timeSteps:[1,1,1],initialField:0,noSeparatorEntry:false,useMouseWheel:true,defaultTime:null,minTime:null,maxTime:null,spinnerImage:'spinnerDefault.png',spinnerSize:[20,20,8],spinnerBigImage:'',spinnerBigSize:[40,40,16],spinnerIncDecOnly:false,spinnerRepeat:[500,250],beforeShow:null,beforeSetTime:null};$.extend(this._defaults,this.regional[''])}$.extend(TimeEntry.prototype,{markerClassName:'hasTimeEntry',propertyName:'timeEntry',_appendClass:'timeEntry_append',_controlClass:'timeEntry_control',_expandClass:'timeEntry_expand',setDefaults:function(a){$.extend(this._defaults,a||{});return this},_attachPlugin:function(b,c){var d=$(b);if(d.hasClass(this.markerClassName)){return}var e={options:$.extend({},this._defaults,c),input:d,_field:0,_selectedHour:0,_selectedMinute:0,_selectedSecond:0};d.data(this.propertyName,e).addClass(this.markerClassName).bind('focus.'+this.propertyName,this._doFocus).bind('blur.'+this.propertyName,this._doBlur).bind('click.'+this.propertyName,this._doClick).bind('keydown.'+this.propertyName,this._doKeyDown).bind('keypress.'+this.propertyName,this._doKeyPress).bind('paste.'+this.propertyName,function(a){setTimeout(function(){n._parseTime(e)},1)});this._optionPlugin(b,c)},_optionPlugin:function(a,b,c){a=$(a);var d=a.data(this.propertyName);if(!b||(typeof b=='string'&&c==null)){var e=b;b=(d||{}).options;return(b&&e?b[e]:b)}if(!a.hasClass(this.markerClassName)){return}b=b||{};if(typeof b=='string'){var e=b;b={};b[e]=c}var f=this._extractTime(d);$.extend(d.options,b);d._field=0;if(f){this._setTime(d,new Date(0,0,0,f[0],f[1],f[2]))}a.next('span.'+this._appendClass).remove();a.parent().find('span.'+this._controlClass).remove();if($.fn.mousewheel){a.unmousewheel()}var g=(!d.options.spinnerImage?null:$(''));a.after(d.options.appendText?''+d.options.appendText+'':'').after(g||'');if(d.options.useMouseWheel&&$.fn.mousewheel){a.mousewheel(this._doMouseWheel)}if(g){g.mousedown(this._handleSpinner).mouseup(this._endSpinner).mouseover(this._expandSpinner).mouseout(this._endSpinner).mousemove(this._describeSpinner)}},_enablePlugin:function(a){this._enableDisable(a,false)},_disablePlugin:function(a){this._enableDisable(a,true)},_enableDisable:function(b,c){var d=$.data(b,this.propertyName);if(!d){return}b.disabled=c;if(b.nextSibling&&b.nextSibling.nodeName.toLowerCase()=='span'){n._changeSpinner(d,b.nextSibling,(c?5:-1))}n._disabledInputs=$.map(n._disabledInputs,function(a){return(a==b?null:a)});if(c){n._disabledInputs.push(b)}},_isDisabledPlugin:function(a){return $.inArray(a,this._disabledInputs)>-1},_destroyPlugin:function(b){b=$(b);if(!b.hasClass(this.markerClassName)){return}b.removeClass(this.markerClassName).removeData(this.propertyName).unbind('.'+this.propertyName);if($.fn.mousewheel){b.unmousewheel()}this._disabledInputs=$.map(this._disabledInputs,function(a){return(a==b[0]?null:a)});b.siblings('.'+this._appendClass+',.'+this._controlClass).remove()},_setTimePlugin:function(a,b){var c=$.data(a,this.propertyName);if(c){if(b===null||b===''){c.input.val('')}else{this._setTime(c,b?(typeof b=='object'?new Date(b.getTime()):b):null)}}},_getTimePlugin:function(a){var b=$.data(a,this.propertyName);var c=(b?this._extractTime(b):null);return(!c?null:new Date(0,0,0,c[0],c[1],c[2]))},_getOffsetPlugin:function(a){var b=$.data(a,this.propertyName);var c=(b?this._extractTime(b):null);return(!c?0:(c[0]*3600+c[1]*60+c[2])*1000)},_doFocus:function(a){var b=(a.nodeName&&a.nodeName.toLowerCase()=='input'?a:this);if(n._lastInput==b||n._isDisabledPlugin(b)){n._focussed=false;return}var c=$.data(b,n.propertyName);n._focussed=true;n._lastInput=b;n._blurredInput=null;$.extend(c.options,($.isFunction(c.options.beforeShow)?c.options.beforeShow.apply(b,[b]):{}));n._parseTime(c);setTimeout(function(){n._showField(c)},10)},_doBlur:function(a){n._blurredInput=n._lastInput;n._lastInput=null},_doClick:function(b){var c=b.target;var d=$.data(c,n.propertyName);var e=d._field;if(!n._focussed){var f=d.options.separator.length+2;d._field=0;if(c.selectionStart!=null){for(var g=0;g<=Math.max(1,d._secondField,d._ampmField);g++){var h=(g!=d._ampmField?(g*f)+2:(d._ampmField*f)+d.options.ampmPrefix.length+d.options.ampmNames[0].length);d._field=g;if(c.selectionStart=48){return true}var b=$.data(a.target,n.propertyName);switch(a.keyCode){case 9:return(a.shiftKey?n._changeField(b,-1,true):n._changeField(b,+1,true));case 35:if(a.ctrlKey){n._setValue(b,'')}else{b._field=Math.max(1,b._secondField,b._ampmField);n._adjustField(b,0)}break;case 36:if(a.ctrlKey){n._setTime(b)}else{b._field=0;n._adjustField(b,0)}break;case 37:n._changeField(b,-1,false);break;case 38:n._adjustField(b,+1);break;case 39:n._changeField(b,+1,false);break;case 40:n._adjustField(b,-1);break;case 46:n._setValue(b,'');break;default:return true}return false},_doKeyPress:function(a){var b=String.fromCharCode(a.charCode==undefined?a.keyCode:a.charCode);if(b<' '){return true}var c=$.data(a.target,n.propertyName);n._handleKeyPress(c,b);return false},_doMouseWheel:function(a,b){if(n._isDisabledPlugin(a.target)){return}var c=$.data(a.target,n.propertyName);c.input.focus();if(!c.input.val()){n._parseTime(c)}n._adjustField(c,b);a.preventDefault()},_expandSpinner:function(b){var c=n._getSpinnerTarget(b);var d=$.data(n._getInput(c),n.propertyName);if(n._isDisabledPlugin(d.input[0])){return}if(d.options.spinnerBigImage){d._expanded=true;var e=$(c).offset();var f=null;$(c).parents().each(function(){var a=$(this);if(a.css('position')=='relative'||a.css('position')=='absolute'){f=a.offset()}return!f});$('
    ').mousedown(n._handleSpinner).mouseup(n._endSpinner).mouseout(n._endExpand).mousemove(n._describeSpinner).insertAfter(c)}},_getInput:function(a){return $(a).siblings('.'+n.markerClassName)[0]},_describeSpinner:function(a){var b=n._getSpinnerTarget(a);var c=$.data(n._getInput(b),n.propertyName);b.title=c.options.spinnerTexts[n._getSpinnerRegion(c,a)]},_handleSpinner:function(a){var b=n._getSpinnerTarget(a);var c=n._getInput(b);if(n._isDisabledPlugin(c)){return}if(c==n._blurredInput){n._lastInput=c;n._blurredInput=null}var d=$.data(c,n.propertyName);n._doFocus(c);var e=n._getSpinnerRegion(d,a);n._changeSpinner(d,b,e);n._actionSpinner(d,e);n._timer=null;n._handlingSpinner=true;if(e>=3&&d.options.spinnerRepeat[0]){n._timer=setTimeout(function(){n._repeatSpinner(d,e)},d.options.spinnerRepeat[0]);$(b).one('mouseout',n._releaseSpinner).one('mouseup',n._releaseSpinner)}},_actionSpinner:function(a,b){if(!a.input.val()){n._parseTime(a)}switch(b){case 0:this._setTime(a);break;case 1:this._changeField(a,-1,false);break;case 2:this._changeField(a,+1,false);break;case 3:this._adjustField(a,+1);break;case 4:this._adjustField(a,-1);break}},_repeatSpinner:function(a,b){if(!n._timer){return}n._lastInput=n._blurredInput;this._actionSpinner(a,b);this._timer=setTimeout(function(){n._repeatSpinner(a,b)},a.options.spinnerRepeat[1])},_releaseSpinner:function(a){clearTimeout(n._timer);n._timer=null},_endExpand:function(a){n._timer=null;var b=n._getSpinnerTarget(a);var c=n._getInput(b);var d=$.data(c,n.propertyName);$(b).remove();d._expanded=false},_endSpinner:function(a){n._timer=null;var b=n._getSpinnerTarget(a);var c=n._getInput(b);var d=$.data(c,n.propertyName);if(!n._isDisabledPlugin(c)){n._changeSpinner(d,b,-1)}if(n._handlingSpinner){n._lastInput=n._blurredInput}if(n._lastInput&&n._handlingSpinner){n._showField(d)}n._handlingSpinner=false},_getSpinnerTarget:function(a){return a.target||a.srcElement},_getSpinnerRegion:function(a,b){var c=this._getSpinnerTarget(b);var d=$(c).offset();var e=[document.documentElement.scrollLeft||document.body.scrollLeft,document.documentElement.scrollTop||document.body.scrollTop];var f=(a.options.spinnerIncDecOnly?99:b.clientX+e[0]-d.left);var g=b.clientY+e[1]-d.top;var h=a.options[a._expanded?'spinnerBigSize':'spinnerSize'];var i=(a.options.spinnerIncDecOnly?99:h[0]-1-f);var j=h[1]-1-g;if(h[2]>0&&Math.abs(f-i)<=h[2]&&Math.abs(g-j)<=h[2]){return 0}var k=Math.min(f,g,i,j);return(k==f?1:(k==i?2:(k==g?3:4)))},_changeSpinner:function(a,b,c){$(b).css('background-position','-'+((c+1)*a.options[a._expanded?'spinnerBigSize':'spinnerSize'][0])+'px 0px')},_parseTime:function(a){var b=this._extractTime(a);if(b){a._selectedHour=b[0];a._selectedMinute=b[1];a._selectedSecond=b[2]}else{var c=this._constrainTime(a);a._selectedHour=c[0];a._selectedMinute=c[1];a._selectedSecond=(a.options.showSeconds?c[2]:0)}a._secondField=(a.options.showSeconds?2:-1);a._ampmField=(a.options.show24Hours?-1:(a.options.showSeconds?3:2));a._lastChr='';a._field=Math.max(0,Math.min(Math.max(1,a._secondField,a._ampmField),a.options.initialField));if(a.input.val()!=''){this._showTime(a)}},_extractTime:function(a,b){b=b||a.input.val();var c=b.split(a.options.separator);if(a.options.separator==''&&b!=''){c[0]=b.substring(0,2);c[1]=b.substring(2,4);c[2]=b.substring(4,6)}if(c.length>=2){var d=!a.options.show24Hours&&(b.indexOf(a.options.ampmNames[0])>-1);var e=!a.options.show24Hours&&(b.indexOf(a.options.ampmNames[1])>-1);var f=parseInt(c[0],10);f=(isNaN(f)?0:f);f=((d||e)&&f==12?0:f)+(e?12:0);var g=parseInt(c[1],10);g=(isNaN(g)?0:g);var h=(c.length>=3?parseInt(c[2],10):0);h=(isNaN(h)||!a.options.showSeconds?0:h);return this._constrainTime(a,[f,g,h])}return null},_constrainTime:function(a,b){var c=(b!=null);if(!c){var d=this._determineTime(a.options.defaultTime,a)||new Date();b=[d.getHours(),d.getMinutes(),d.getSeconds()]}var e=false;for(var i=0;i1){b[i]=Math.round(b[i]/a.options.timeSteps[i])*a.options.timeSteps[i];e=true}}return b},_showTime:function(a){var b=(this._formatNumber(a.options.show24Hours?a._selectedHour:((a._selectedHour+11)%12)+1)+a.options.separator+this._formatNumber(a._selectedMinute)+(a.options.showSeconds?a.options.separator+this._formatNumber(a._selectedSecond):'')+(a.options.show24Hours?'':a.options.ampmPrefix+a.options.ampmNames[(a._selectedHour<12?0:1)]));this._setValue(a,b);this._showField(a)},_showField:function(a){var b=a.input[0];if(a.input.is(':hidden')||n._lastInput!=b){return}var c=a.options.separator.length+2;var d=(a._field!=a._ampmField?(a._field*c):(a._ampmField*c)-a.options.separator.length+a.options.ampmPrefix.length);var e=d+(a._field!=a._ampmField?2:a.options.ampmNames[0].length);if(b.setSelectionRange){b.setSelectionRange(d,e)}else if(b.createTextRange){var f=b.createTextRange();f.moveStart('character',d);f.moveEnd('character',e-a.input.val().length);f.select()}if(!b.disabled){b.focus()}},_formatNumber:function(a){return(a<10?'0':'')+a},_setValue:function(a,b){if(b!=a.input.val()){a.input.val(b).trigger('change')}},_changeField:function(a,b,c){var d=(a.input.val()==''||a._field==(b==-1?0:Math.max(1,a._secondField,a._ampmField)));if(!d){a._field+=b}this._showField(a);a._lastChr='';return(d&&c)},_adjustField:function(a,b){if(a.input.val()==''){b=0}this._setTime(a,new Date(0,0,0,a._selectedHour+(a._field==0?b*a.options.timeSteps[0]:0)+(a._field==a._ampmField?b*12:0),a._selectedMinute+(a._field==1?b*a.options.timeSteps[1]:0),a._selectedSecond+(a._field==a._secondField?b*a.options.timeSteps[2]:0)))},_setTime:function(a,b){b=this._determineTime(b,a);var c=this._constrainTime(a,b?[b.getHours(),b.getMinutes(),b.getSeconds()]:null);b=new Date(0,0,0,c[0],c[1],c[2]);var b=this._normaliseTime(b);var d=this._normaliseTime(this._determineTime(a.options.minTime,a));var e=this._normaliseTime(this._determineTime(a.options.maxTime,a));if(d&&e&&d>e){if(be){b=(Math.abs(b-d)e?e:b))}if($.isFunction(a.options.beforeSetTime)){b=a.options.beforeSetTime.apply(a.input[0],[this._getTimePlugin(a.input[0]),b,d,e])}a._selectedHour=b.getHours();a._selectedMinute=b.getMinutes();a._selectedSecond=b.getSeconds();this._showTime(a)},_determineTime:function(i,j){var k=function(a){var b=new Date();b.setTime(b.getTime()+a*1000);return b};var l=function(a){var b=n._extractTime(j,a);var c=new Date();var d=(b?b[0]:c.getHours());var e=(b?b[1]:c.getMinutes());var f=(b?b[2]:c.getSeconds());if(!b){var g=/([+-]?[0-9]+)\s*(s|S|m|M|h|H)?/g;var h=g.exec(a);while(h){switch(h[2]||'s'){case's':case'S':f+=parseInt(h[1],10);break;case'm':case'M':e+=parseInt(h[1],10);break;case'h':case'H':d+=parseInt(h[1],10);break}h=g.exec(a)}}c=new Date(0,0,10,d,e,f,0);if(/^!/.test(a)){if(c.getDate()>10){c=new Date(0,0,10,23,59,59)}else if(c.getDate()<10){c=new Date(0,0,10,0,0,0)}}return c};return(i?(typeof i=='string'?l(i):(typeof i=='number'?k(i):i)):null)},_normaliseTime:function(a){if(!a){return null}a.setFullYear(1900);a.setMonth(0);a.setDate(0);return a},_handleKeyPress:function(a,b){if(b==a.options.separator){this._changeField(a,+1,false)}else if(b>='0'&&b<='9'){var c=parseInt(b,10);var d=parseInt(a._lastChr+b,10);var e=(a._field!=0?a._selectedHour:(a.options.show24Hours?(d<24?d:c):(d>=1&&d<=12?d:(c>0?c:a._selectedHour))%12+(a._selectedHour>=12?12:0)));var f=(a._field!=1?a._selectedMinute:(d<60?d:c));var g=(a._field!=a._secondField?a._selectedSecond:(d<60?d:c));var h=this._constrainTime(a,[e,f,g]);this._setTime(a,new Date(0,0,0,h[0],h[1],h[2]));if(a.options.noSeparatorEntry&&a._lastChr){this._changeField(a,+1,false)}else{a._lastChr=b}}else if(!a.options.show24Hours){b=b.toLowerCase();if((b==a.options.ampmNames[0].substring(0,1).toLowerCase()&&a._selectedHour>=12)||(b==a.options.ampmNames[1].substring(0,1).toLowerCase()&&a._selectedHour<12)){var i=a._field;a._field=a._ampmField;this._adjustField(a,+1);a._field=i;this._showField(a)}}}});var m=['getOffset','getTime','isDisabled'];function isNotChained(a,b){if(a=='option'&&(b.length==0||(b.length==1&&typeof b[0]=='string'))){return true}return $.inArray(a,m)>-1}$.fn.timeEntry=function(b){var c=Array.prototype.slice.call(arguments,1);if(isNotChained(b,c)){return n['_'+b+'Plugin'].apply(n,[this[0]].concat(c))}return this.each(function(){if(typeof b=='string'){if(!n['_'+b+'Plugin']){throw'Unknown command: '+b;}n['_'+b+'Plugin'].apply(n,[this].concat(c))}else{var a=($.fn.metadata?$(this).metadata():{});n._attachPlugin(this,$.extend({},a,b||{}))}})};var n=$.timeEntry=new TimeEntry()})(jQuery); +/* jshint ignore:end */ \ No newline at end of file diff --git a/sites/all/modules/contrib/fields/date/date_repeat.inc b/sites/all/modules/contrib/fields/date/date_repeat.inc index 2df03dc..046d8ae 100644 --- a/sites/all/modules/contrib/fields/date/date_repeat.inc +++ b/sites/all/modules/contrib/fields/date/date_repeat.inc @@ -2,5 +2,6 @@ /** * @file * Empty file to avoid fatal error if it doesn't exist. + * * Formerly the Date Repeat field code. - */ \ No newline at end of file + */ diff --git a/sites/all/modules/contrib/fields/date/date_repeat/date_repeat.info b/sites/all/modules/contrib/fields/date/date_repeat/date_repeat.info index 1d1720a..0a63175 100644 --- a/sites/all/modules/contrib/fields/date/date_repeat/date_repeat.info +++ b/sites/all/modules/contrib/fields/date/date_repeat/date_repeat.info @@ -7,9 +7,9 @@ php = 5.2 files[] = tests/date_repeat.test files[] = tests/date_repeat_form.test -; Information added by drupal.org packaging script on 2012-08-13 -version = "7.x-2.6" +; Information added by Drupal.org packaging script on 2017-04-07 +version = "7.x-2.10" core = "7.x" project = "date" -datestamp = "1344850024" +datestamp = "1491562090" diff --git a/sites/all/modules/contrib/fields/date/date_repeat/date_repeat.install b/sites/all/modules/contrib/fields/date/date_repeat/date_repeat.install index 65c3adb..5203963 100644 --- a/sites/all/modules/contrib/fields/date/date_repeat/date_repeat.install +++ b/sites/all/modules/contrib/fields/date/date_repeat/date_repeat.install @@ -12,21 +12,3 @@ function date_repeat_install() { // Make sure this module loads after date_api. db_query("UPDATE {system} SET weight = 1 WHERE name = 'date_repeat'"); } - -/** - * Implements hook_uninstall(). - */ -function date_repeat_uninstall() { -} - -/** - * Implements hook_enable(). - */ -function date_repeat_enable() { -} - -/** - * Implements hook_disable(). - */ -function date_repeat_disable() { -} diff --git a/sites/all/modules/contrib/fields/date/date_repeat/date_repeat.module b/sites/all/modules/contrib/fields/date/date_repeat/date_repeat.module index d742dc8..61d971b 100644 --- a/sites/all/modules/contrib/fields/date/date_repeat/date_repeat.module +++ b/sites/all/modules/contrib/fields/date/date_repeat/date_repeat.module @@ -1,7 +1,6 @@ array('render element' => 'element'), @@ -55,6 +57,9 @@ function date_repeat_freq_options() { ); } +/** + * Helper function for interval options. + */ function date_repeat_interval_options() { $options = range(0, 366); unset($options[0]); @@ -92,9 +97,11 @@ function date_repeat_dow_day_options_abbr($translated = TRUE, $length = 3) { case 1: $context = 'day_abbr1'; break; + case 2: $context = 'day_abbr2'; break; + default: $context = ''; break; @@ -105,16 +112,28 @@ function date_repeat_dow_day_options_abbr($translated = TRUE, $length = 3) { return $return; } +/** + * Helper function for weekdays translated. + */ function date_repeat_dow_day_untranslated() { static $date_repeat_weekdays; if (empty($date_repeat_weekdays)) { - $date_repeat_weekdays = array('SU' => 'Sunday', 'MO' => 'Monday', 'TU' => 'Tuesday', - 'WE' => 'Wednesday', 'TH' => 'Thursday', 'FR' => 'Friday', - 'SA' => 'Saturday'); + $date_repeat_weekdays = array( + 'SU' => 'Sunday', + 'MO' => 'Monday', + 'TU' => 'Tuesday', + 'WE' => 'Wednesday', + 'TH' => 'Thursday', + 'FR' => 'Friday', + 'SA' => 'Saturday' + ); } return $date_repeat_weekdays; } +/** + * Helper function for weekdays order. + */ function date_repeat_dow_day_options_ordered($weekdays) { $day_keys = array_keys($weekdays); $day_values = array_values($weekdays); @@ -164,8 +183,7 @@ function date_repeat_dow2day($dow) { } /** - * Shift the array of iCal day names into the right order - * for a specific week start day. + * Shift the array of iCal day names into the right order for a specific week start day. */ function date_repeat_days_ordered($week_start_day) { $days = array_flip(array_keys(date_repeat_dow_day_options(FALSE))); @@ -212,18 +230,21 @@ function date_repeat_rrule_description($rrule, $format = 'D M d Y') { '!except' => '', '!additional' => '', '!week_starts_on' => '', - ); + ); $interval = date_repeat_interval_options(); switch ($rrule['FREQ']) { case 'WEEKLY': $description['!interval'] = format_plural($rrule['INTERVAL'], 'every week', 'every @count weeks') . ' '; break; + case 'MONTHLY': $description['!interval'] = format_plural($rrule['INTERVAL'], 'every month', 'every @count months') . ' '; break; + case 'YEARLY': $description['!interval'] = format_plural($rrule['INTERVAL'], 'every year', 'every @count years') . ' '; break; + default: $description['!interval'] = format_plural($rrule['INTERVAL'], 'every day', 'every @count days') . ' '; break; @@ -240,26 +261,41 @@ function date_repeat_rrule_description($rrule, $format = 'D M d Y') { if (!empty($count)) { // See if there is a 'pretty' option for this count, i.e. +1 => First. $order = array_key_exists($count, $counts) ? strtolower($counts[$count]) : $count; - $results[] = trim(t('!repeats_every_interval on the !date_order !day_of_week', array('!repeats_every_interval ' => '', '!date_order' => $order, '!day_of_week' => $days[$day]))); + $results[] = trim(t('!repeats_every_interval on the !date_order !day_of_week', + array( + '!repeats_every_interval ' => '', + '!date_order' => $order, + '!day_of_week' => $days[$day] + ))); } else { - $results[] = trim(t('!repeats_every_interval every !day_of_week', array('!repeats_every_interval ' => '', '!day_of_week' => $days[$day]))); + $results[] = trim(t('!repeats_every_interval every !day_of_week', + array('!repeats_every_interval ' => '', '!day_of_week' => $days[$day]))); } } $description['!byday'] = implode(' ' . t('and') . ' ', $results); } if (!empty($rrule['BYMONTH'])) { - if (sizeof($rrule['BYMONTH']) < 12) { + if (count($rrule['BYMONTH']) < 12) { $results = array(); $months = date_month_names(); foreach ($rrule['BYMONTH'] as $month) { $results[] = $months[$month]; } if (!empty($rrule['BYMONTHDAY'])) { - $description['!bymonth'] = trim(t('!repeats_every_interval on the !month_days of !month_names', array('!repeats_every_interval ' => '', '!month_days' => implode(', ', $rrule['BYMONTHDAY']), '!month_names' => implode(', ', $results)))); + $description['!bymonth'] = trim(t('!repeats_every_interval on the !month_days of !month_names', + array( + '!repeats_every_interval ' => '', + '!month_days' => implode(', ', $rrule['BYMONTHDAY']), + '!month_names' => implode(', ', $results) + ))); } else { - $description['!bymonth'] = trim(t('!repeats_every_interval on !month_names', array('!repeats_every_interval ' => '', '!month_names' => implode(', ', $results)))); + $description['!bymonth'] = trim(t('!repeats_every_interval on !month_names', + array( + '!repeats_every_interval ' => '', + '!month_names' => implode(', ', $results) + ))); } } } @@ -267,12 +303,17 @@ function date_repeat_rrule_description($rrule, $format = 'D M d Y') { $rrule['INTERVAL'] = 1; } if (!empty($rrule['COUNT'])) { - $description['!count'] = trim(t('!repeats_every_interval !count times', array('!repeats_every_interval ' => '', '!count' => $rrule['COUNT']))); + $description['!count'] = trim(t('!repeats_every_interval !count times', + array('!repeats_every_interval ' => '', '!count' => $rrule['COUNT']))); } if (!empty($rrule['UNTIL'])) { $until = date_ical_date($rrule['UNTIL'], 'UTC'); date_timezone_set($until, date_default_timezone_object()); - $description['!until'] = trim(t('!repeats_every_interval until !until_date', array('!repeats_every_interval ' => '', '!until_date' => date_format_date($until, 'custom', $format)))); + $description['!until'] = trim(t('!repeats_every_interval until !until_date', + array( + '!repeats_every_interval ' => '', + '!until_date' => date_format_date($until, 'custom', $format) + ))); } if ($exceptions) { $values = array(); @@ -281,11 +322,16 @@ function date_repeat_rrule_description($rrule, $format = 'D M d Y') { date_timezone_set($except, date_default_timezone_object()); $values[] = date_format_date($except, 'custom', $format); } - $description['!except'] = trim(t('!repeats_every_interval except !except_dates', array('!repeats_every_interval ' => '', '!except_dates' => implode(', ', $values)))); + $description['!except'] = trim(t('!repeats_every_interval except !except_dates', + array( + '!repeats_every_interval ' => '', + '!except_dates' => implode(', ', $values) + ))); } if (!empty($rrule['WKST'])) { $day_names = date_repeat_dow_day_options(); - $description['!week_starts_on'] = trim(t('!repeats_every_interval where the week start on !day_of_week', array('!repeats_every_interval ' => '', '!day_of_week' => $day_names[trim($rrule['WKST'])]))); + $description['!week_starts_on'] = trim(t('!repeats_every_interval where the week start on !day_of_week', + array('!repeats_every_interval ' => '', '!day_of_week' => $day_names[trim($rrule['WKST'])]))); } if ($additions) { $values = array(); @@ -294,9 +340,15 @@ function date_repeat_rrule_description($rrule, $format = 'D M d Y') { date_timezone_set($add, date_default_timezone_object()); $values[] = date_format_date($add, 'custom', $format); } - $description['!additional'] = trim(t('Also includes !additional_dates.', array('!additional_dates' => implode(', ', $values)))); + $description['!additional'] = trim(t('Also includes !additional_dates.', + array('!additional_dates' => implode(', ', $values)))); } - return t('Repeats !interval !bymonth !byday !count !until !except. !additional', $description); + $output = t('Repeats !interval !bymonth !byday !count !until !except. !additional', $description); + // Removes double whitespaces from Repeat tile. + $output = preg_replace('/\s+/', ' ', $output); + // Removes whitespace before full stop ".", at the end of the title. + $output = str_replace(' .', '.', $output); + return $output; } /** @@ -310,17 +362,17 @@ function date_repeat_split_rrule($rrule) { $additions = array(); foreach ($parts as $part) { if (strstr($part, 'RRULE')) { - $RRULE = str_replace('RRULE:', '', $part); - $rrule = (array) date_ical_parse_rrule('RRULE:', $RRULE); + $cleanded_part = str_replace('RRULE:', '', $part); + $rrule = (array) date_ical_parse_rrule('RRULE:', $cleanded_part); } elseif (strstr($part, 'EXDATE')) { - $EXDATE = str_replace('EXDATE:', '', $part); - $exceptions = (array) date_ical_parse_exceptions('EXDATE:', $EXDATE); + $exdate = str_replace('EXDATE:', '', $part); + $exceptions = (array) date_ical_parse_exceptions('EXDATE:', $exdate); unset($exceptions['DATA']); } elseif (strstr($part, 'RDATE')) { - $RDATE = str_replace('RDATE:', '', $part); - $additions = (array) date_ical_parse_exceptions('RDATE:', $RDATE); + $rdate = str_replace('RDATE:', '', $part); + $additions = (array) date_ical_parse_exceptions('RDATE:', $rdate); unset($additions['DATA']); } } @@ -372,7 +424,7 @@ function date_repeat_form_element_radios_process($element) { '#title_display' => 'invisible', '#return_value' => $key, '#default_value' => isset($element['#default_value']) ? - $element['#default_value'] : NULL, + $element['#default_value'] : NULL, '#attributes' => $element['#attributes'], '#parents' => $element['#parents'], '#id' => drupal_html_id('edit-' . implode('-', $parents_for_id)), diff --git a/sites/all/modules/contrib/fields/date/date_repeat/date_repeat_calc.inc b/sites/all/modules/contrib/fields/date/date_repeat/date_repeat_calc.inc index 2a02d94..d840df6 100644 --- a/sites/all/modules/contrib/fields/date/date_repeat/date_repeat_calc.inc +++ b/sites/all/modules/contrib/fields/date/date_repeat/date_repeat_calc.inc @@ -53,9 +53,11 @@ function _date_repeat_calc($rrule, $start, $end, $exceptions, $timezone, $additi // Create a date object for the start and end dates. $start_date = new DateObject($start, $timezone); - // Versions of PHP greater than PHP 5.3.5 require that we set an explicit time when - // using date_modify() or the time may not match the original value. Adding this - // modifier gives us the same results in both older and newer versions of PHP. + // Versions of PHP greater than PHP 5.3.5 require + // that we set an explicit time when using date_modify() + // or the time may not match the original value. + // Adding this modifier gives us the same results in both older + // and newer versions of PHP. $modify_time = ' ' . $start_date->format('g:ia'); // If the rule has an UNTIL, see if that is earlier than the end date. @@ -91,27 +93,32 @@ function _date_repeat_calc($rrule, $start, $end, $exceptions, $timezone, $additi } // Make sure DAILY frequency isn't used in places it won't work; - if (!empty($rrule['BYMONTHDAY']) && !in_array($rrule['FREQ'], array('MONTHLY', 'YEARLY'))) { + if (!empty($rrule['BYMONTHDAY']) && + !in_array($rrule['FREQ'], array('MONTHLY', 'YEARLY'))) { $rrule['FREQ'] = 'MONTHLY'; } - elseif (!empty($rrule['BYDAY']) && !in_array($rrule['FREQ'], array('MONTHLY', 'WEEKLY', 'YEARLY'))) { + elseif (!empty($rrule['BYDAY']) + && !in_array($rrule['FREQ'], array('MONTHLY', 'WEEKLY', 'YEARLY'))) { $rrule['FREQ'] = 'WEEKLY'; - } + } // Find the time period to jump forward between dates. switch ($rrule['FREQ']) { - case 'DAILY': - $jump = $interval . ' days'; - break; - case 'WEEKLY': - $jump = $interval . ' weeks'; - break; - case 'MONTHLY': - $jump = $interval . ' months'; - break; - case 'YEARLY': - $jump = $interval . ' years'; - break; + case 'DAILY': + $jump = $interval . ' days'; + break; + + case 'WEEKLY': + $jump = $interval . ' weeks'; + break; + + case 'MONTHLY': + $jump = $interval . ' months'; + break; + + case 'YEARLY': + $jump = $interval . ' years'; + break; } $rrule = date_repeat_adjust_rrule($rrule, $start_date); @@ -135,7 +142,7 @@ function _date_repeat_calc($rrule, $start, $end, $exceptions, $timezone, $additi $direction_days[$day] = array( 'direction' => !empty($regs[1]) ? $regs[1] : '+', 'direction_count' => $regs[2], - ); + ); } } while (!$finished) { @@ -173,7 +180,7 @@ function _date_repeat_calc($rrule, $start, $end, $exceptions, $timezone, $additi } if ($rrule['FREQ'] == 'YEARLY') { // Back up to first of year and jump. - $current_day = date_repeat_set_year_day($current_day, NULL, 1, '+', $timezone, $modify_time); + $current_day = date_repeat_set_year_day($current_day, NULL, NULL, 1, '+', $timezone, $modify_time); date_modify($current_day, '+' . $jump . $modify_time); } $finished = date_repeat_is_finished($current_day, $days, $count, $end_date); @@ -198,8 +205,9 @@ function _date_repeat_calc($rrule, $start, $end, $exceptions, $timezone, $additi else { - // More complex searches for day names and criteria like '-1SU' or '2TU,2TH', - // require that we interate through the whole time period checking each BYDAY. + // More complex searches for day names and criteria + // like '-1SU' or '2TU,2TH', require that we interate through + // the whole time period checking each BYDAY. // Create helper array to pull day names out of iCal day strings. $day_names = date_repeat_dow_day_options(FALSE); @@ -223,18 +231,42 @@ function _date_repeat_calc($rrule, $start, $end, $exceptions, $timezone, $additi $ordered = date_repeat_days_ordered($week_start_rule); $ordered_keys = array_flip($ordered); - foreach ($rrule['BYDAY'] as $day) { - preg_match("@(-)?([0-9]+)?([SU|MO|TU|WE|TH|FR|SA]{2})@", trim($day), $regs); - if (!empty($regs[2])) { - // Convert parameters into full day name, count, and direction. - $direction_days[] = array( - 'day' => $day_names[$regs[3]], - 'direction' => !empty($regs[1]) ? $regs[1] : '+', - 'direction_count' => $regs[2], - ); + if ($rrule['FREQ'] == 'YEARLY' && !empty($rrule['BYMONTH'])) { + // Additional cycle to apply month preferences. + foreach ($rrule['BYMONTH'] as $month) { + foreach ($rrule['BYDAY'] as $day) { + preg_match("@(-)?([0-9]+)?([SU|MO|TU|WE|TH|FR|SA]{2})@", trim($day), $regs); + // Convert parameters into full day name, count, and direction. + // Add leading zero to first 9 months. + if (!empty($regs[2])) { + $direction_days[] = array( + 'day' => $day_names[$regs[3]], + 'direction' => !empty($regs[1]) ? $regs[1] : '+', + 'direction_count' => $regs[2], + 'month' => strlen($month) > 1 ? $month : '0' . $month, + ); + } + else { + $week_days[$ordered_keys[$regs[3]]] = $day_names[$regs[3]]; + } + } } - else { - $week_days[$ordered_keys[$regs[3]]] = $day_names[$regs[3]]; + } + else { + foreach ($rrule['BYDAY'] as $day) { + preg_match("@(-)?([0-9]+)?([SU|MO|TU|WE|TH|FR|SA]{2})@", trim($day), $regs); + if (!empty($regs[2])) { + // Convert parameters into full day name, count, and direction. + $direction_days[] = array( + 'day' => $day_names[$regs[3]], + 'direction' => !empty($regs[1]) ? $regs[1] : '+', + 'direction_count' => $regs[2], + 'month' => NULL, + ); + } + else { + $week_days[$ordered_keys[$regs[3]]] = $day_names[$regs[3]]; + } } } ksort($week_days); @@ -251,7 +283,7 @@ function _date_repeat_calc($rrule, $start, $end, $exceptions, $timezone, $additi $current_day = date_repeat_set_month_day($current_day, $day['day'], $day['direction_count'], $day['direction'], $timezone, $modify_time); } else { - $current_day = date_repeat_set_year_day($current_day, $day['day'], $day['direction_count'], $day['direction'], $timezone, $modify_time); + $current_day = date_repeat_set_year_day($current_day, $day['month'], $day['day'], $day['direction_count'], $day['direction'], $timezone, $modify_time); } date_repeat_add_dates($days, $current_day, $start_date, $end_date, $exceptions, $rrule); } @@ -279,7 +311,8 @@ function _date_repeat_calc($rrule, $start, $end, $exceptions, $timezone, $additi // period, then jumping ahead to the next week, month, or year, // an INTERVAL at a time. - if (!empty($week_days) && in_array($rrule['FREQ'], array('MONTHLY', 'WEEKLY', 'YEARLY'))) { + if (!empty($week_days) && + in_array($rrule['FREQ'], array('MONTHLY', 'WEEKLY', 'YEARLY'))) { $finished = FALSE; $current_day = clone($start_date); $format = $rrule['FREQ'] == 'YEARLY' ? 'Y' : 'n'; @@ -298,8 +331,9 @@ function _date_repeat_calc($rrule, $start, $end, $exceptions, $timezone, $additi $moved = FALSE; foreach ($week_days as $delta => $day) { // Find the next occurence of each day in this week, only add it - // if we are still in the current month or year. The date_repeat_add_dates - // function is insufficient to test whether to include this date + // if we are still in the current month or year. + // The date_repeat_add_dates function is insufficient + // to test whether to include this date // if we are using a rule like 'every other month', so we must // explicitly test it here. @@ -346,10 +380,12 @@ function _date_repeat_calc($rrule, $start, $end, $exceptions, $timezone, $additi date_modify($current_day, '+1 ' . $week_start_day . $modify_time); date_modify($current_day, '-1 week' . $modify_time); break; + case 'MONTHLY': date_modify($current_day, '-' . (date_format($current_day, 'j') - 1) . ' days' . $modify_time); date_modify($current_day, '-1 month' . $modify_time); break; + case 'YEARLY': date_modify($current_day, '-' . date_format($current_day, 'z') . ' days' . $modify_time); date_modify($current_day, '-1 year' . $modify_time); @@ -363,7 +399,7 @@ function _date_repeat_calc($rrule, $start, $end, $exceptions, $timezone, $additi } } - // add additional dates + // Add additional dates. foreach ($additions as $addition) { $date = new dateObject($addition . ' ' . $start_date->format('H:i:s'), $timezone); $days[] = date_format($date, DATE_FORMAT_DATETIME); @@ -402,8 +438,8 @@ function date_repeat_adjust_rrule($rrule, $start_date) { // position rules make no sense in other periods and just add complexity. elseif (!empty($rrule['BYDAY']) && !in_array($rrule['FREQ'], array('MONTHLY', 'YEARLY'))) { - foreach ($rrule['BYDAY'] as $delta => $BYDAY) { - $rrule['BYDAY'][$delta] = substr($BYDAY, -2); + foreach ($rrule['BYDAY'] as $delta => $by_day) { + $rrule['BYDAY'][$delta] = substr($by_day, -2); } } @@ -418,7 +454,7 @@ function date_repeat_adjust_rrule($rrule, $start_date) { * and that it meets other criteria in the RRULE. */ function date_repeat_add_dates(&$days, $current_day, $start_date, $end_date, $exceptions, $rrule) { - if (isset($rrule['COUNT']) && sizeof($days) >= $rrule['COUNT']) { + if (isset($rrule['COUNT']) && count($days) >= $rrule['COUNT']) { return FALSE; } $formatted = date_format($current_day, DATE_FORMAT_DATETIME); @@ -432,13 +468,14 @@ function date_repeat_add_dates(&$days, $current_day, $start_date, $end_date, $ex return FALSE; } if (!empty($rrule['BYDAY'])) { - $BYDAYS = $rrule['BYDAY']; - foreach ($BYDAYS as $delta => $BYDAY) { - $BYDAYS[$delta] = substr($BYDAY, -2); + $by_days = $rrule['BYDAY']; + foreach ($by_days as $delta => $by_day) { + $by_days[$delta] = substr($by_day, -2); } - if (!in_array(date_repeat_dow2day(date_format($current_day, 'w')), $BYDAYS)) { + if (!in_array(date_repeat_dow2day(date_format($current_day, 'w')), $by_days)) { return FALSE; - }} + } + } if (!empty($rrule['BYYEAR']) && !in_array(date_format($current_day, 'Y'), $rrule['BYYEAR'])) { return FALSE; } @@ -448,17 +485,17 @@ function date_repeat_add_dates(&$days, $current_day, $start_date, $end_date, $ex if (!empty($rrule['BYMONTHDAY'])) { // Test month days, but only if there are no negative numbers. $test = TRUE; - $BYMONTHDAYS = array(); + $by_month_days = array(); foreach ($rrule['BYMONTHDAY'] as $day) { if ($day > 0) { - $BYMONTHDAYS[] = $day; + $by_month_days[] = $day; } else { $test = FALSE; break; } } - if ($test && !empty($BYMONTHDAYS) && !in_array(date_format($current_day, 'j'), $BYMONTHDAYS)) { + if ($test && !empty($by_month_days) && !in_array(date_format($current_day, 'j'), $by_month_days)) { return FALSE; } } @@ -475,7 +512,7 @@ function date_repeat_add_dates(&$days, $current_day, $start_date, $end_date, $ex * Stop when $current_day is greater than $end_date or $count is reached. */ function date_repeat_is_finished($current_day, $days, $count, $end_date) { - if (($count && sizeof($days) >= $count) + if (($count && count($days) >= $count) || (!empty($end_date) && date_format($current_day, 'U') > date_format($end_date, 'U'))) { return TRUE; } @@ -493,7 +530,7 @@ function date_repeat_is_finished($current_day, $days, $count, $end_date) { * If $day is empty, will set to the number of days from the * beginning or end of the month. */ -function date_repeat_set_month_day($date_in, $day, $count = 1, $direction = '+', $timezone = 'UTC', $modify_time) { +function date_repeat_set_month_day($date_in, $day, $count = 1, $direction = '+', $timezone = 'UTC', $modify_time = '') { if (is_object($date_in)) { $current_month = date_format($date_in, 'n'); @@ -543,24 +580,42 @@ function date_repeat_set_month_day($date_in, $day, $count = 1, $direction = '+', * If $day is empty, will set to the number of days from the * beginning or end of the year. */ -function date_repeat_set_year_day($date_in, $day, $count = 1, $direction = '+', $timezone = 'UTC', $modify_time) { +function date_repeat_set_year_day($date_in, $month, $day, $count = 1, $direction = '+', $timezone = 'UTC', $modify_time = '') { if (is_object($date_in)) { $current_year = date_format($date_in, 'Y'); // Reset to the start of the month. // See note above. $datetime = date_format($date_in, DATE_FORMAT_DATETIME); - $datetime = substr_replace($datetime, '01-01', 5, 5); + $month_key = isset($month) ? $month : '01'; + $datetime = substr_replace($datetime, $month_key . '-01', 5, 5); $date = new DateObject($datetime, $timezone); - if ($direction == '-') { - // For negative search, start from the end of the year. - date_modify($date, '+1 year' . $modify_time); + + if (isset($month)) { + if ($direction == '-') { + // For negative search, start from the end of the month. + $modifier = '+1 month'; + } + else { + // For positive search, back up one day to get outside the + // current month, so we can catch the first of the month. + $modifier = '-1 day'; + } } else { - // For positive search, back up one day to get outside the - // current year, so we can catch the first of the year. - date_modify($date, '-1 day' . $modify_time); + if ($direction == '-') { + // For negative search, start from the end of the year. + $modifier = '+1 year'; + } + else { + // For positive search, back up one day to get outside the + // current year, so we can catch the first of the year. + $modifier = '-1 day'; + } } + + date_modify($date, $modifier . $modify_time); + if (empty($day)) { date_modify($date, $direction . $count . ' days' . $modify_time); } @@ -578,4 +633,4 @@ function date_repeat_set_year_day($date_in, $day, $count = 1, $direction = '+', } } return $date_in; -} \ No newline at end of file +} diff --git a/sites/all/modules/contrib/fields/date/date_repeat/date_repeat_form.inc b/sites/all/modules/contrib/fields/date/date_repeat/date_repeat_form.inc index 9cef000..3352595 100644 --- a/sites/all/modules/contrib/fields/date/date_repeat/date_repeat_form.inc +++ b/sites/all/modules/contrib/fields/date/date_repeat/date_repeat_form.inc @@ -30,13 +30,16 @@ * BYSETPOS * Seldom used anywhere, so no reason to complicated the code. */ + /** * Generate the repeat setting form. */ function _date_repeat_rrule_process($element, &$form_state, $form) { - // If the RRULE field is not visible to the user, needs no processing or validation. - // The Date field module is not adding this element to forms if the field is hidden, + // If the RRULE field is not visible to the user, + // needs no processing or validation. + // The Date field module is not adding this element to forms + // if the field is hidden, // this test is just in case some other module attempts to do so. if (date_hidden_element($element)) { @@ -67,16 +70,16 @@ function _date_repeat_rrule_process($element, &$form_state, $form) { $timezone = !empty($element['#date_timezone']) ? $element['#date_timezone'] : date_default_timezone(); $merged_values = date_repeat_merge($rrule, $element); - $UNTIL = ''; + $until = ''; if (!empty($merged_values['UNTIL']['datetime'])) { $until_date = new DateObject($merged_values['UNTIL']['datetime'], $merged_values['UNTIL']['tz']); date_timezone_set($until_date, timezone_open($timezone)); - $UNTIL = date_format($until_date, DATE_FORMAT_DATETIME); + $until = date_format($until_date, DATE_FORMAT_DATETIME); } - $COUNT = ''; + $count = ''; if (!empty($merged_values['COUNT'])) { - $COUNT = $merged_values['COUNT']; + $count = $merged_values['COUNT']; } $element['FREQ'] = array( @@ -137,7 +140,7 @@ function _date_repeat_rrule_process($element, &$form_state, $form) { ); list($prefix, $suffix) = explode('@interval', t('Every @interval days', array(), array('context' => 'Date repeat'))); - $DAILY_INTERVAL = array( + $daily_interval = array( '#type' => 'textfield', '#title' => t('Repeats', array(), array('context' => 'Date repeat')), '#title_display' => 'invisible', @@ -210,32 +213,34 @@ function _date_repeat_rrule_process($element, &$form_state, $form) { '#suffix' => '', ); - $DAILY_radios_default = 'INTERVAL'; + $daily_radios_default = 'INTERVAL'; if (isset($rrule['FREQ']) && $rrule['FREQ'] === 'DAILY' && !empty($rrule['BYDAY'])) { switch (count($rrule['BYDAY'])) { case 2: - $DAILY_radios_default = 'every_tu_th'; + $daily_radios_default = 'every_tu_th'; break; + case 3: - $DAILY_radios_default = 'every_mo_we_fr'; + $daily_radios_default = 'every_mo_we_fr'; break; + case 5: - $DAILY_radios_default = 'every_weekday'; + $daily_radios_default = 'every_weekday'; break; } } - $DAILY_every_weekday = array( + $daily_every_weekday = array( '#type' => 'item', '#markup' => '
    ' . t('Every weekday', array(), array('context' => 'Date repeat')) . '
    ', ); - $DAILY_mo_we_fr = array( + $daily_mo_we_fr = array( '#type' => 'item', '#markup' => '
    ' . t('Every Mon, Wed, Fri', array(), array('context' => 'Date repeat')) . '
    ', ); - $DAILY_tu_th = array( + $daily_tu_th = array( '#type' => 'item', '#markup' => '
    ' . t('Every Tue, Thu', array(), array('context' => 'Date repeat')) . '
    ', ); @@ -251,17 +256,17 @@ function _date_repeat_rrule_process($element, &$form_state, $form) { ":input[name=\"{$element['#name']}[FREQ]\"]" => array('value' => 'DAILY'), ), ), - '#default_value' => $DAILY_radios_default, + '#default_value' => $daily_radios_default, '#options' => array( 'INTERVAL' => t('interval'), 'every_weekday' => t('every weekday'), 'every_mo_we_fr' => t('monday wednesday friday'), 'every_tu_th' => t('tuesday thursday'), ), - 'INTERVAL_child' => $DAILY_INTERVAL, - 'every_weekday_child' => $DAILY_every_weekday, - 'mo_we_fr_child' => $DAILY_mo_we_fr, - 'tu_th_child' => $DAILY_tu_th, + 'INTERVAL_child' => $daily_interval, + 'every_weekday_child' => $daily_every_weekday, + 'mo_we_fr_child' => $daily_mo_we_fr, + 'tu_th_child' => $daily_tu_th, '#div_classes' => array( 'container-inline interval', 'container-inline weekday', @@ -270,18 +275,18 @@ function _date_repeat_rrule_process($element, &$form_state, $form) { ), ); - $MONTHLY_day_month_default = 'BYMONTHDAY_BYMONTH'; + $monthly_day_month_default = 'BYMONTHDAY_BYMONTH'; if (isset($rrule['FREQ']) && $rrule['FREQ'] === 'MONTHLY' && !empty($rrule['BYDAY'])) { - $MONTHLY_day_month_default = 'BYDAY_BYMONTH'; + $monthly_day_month_default = 'BYDAY_BYMONTH'; } - $MONTHLY_on_day_BYMONTHDAY_of_BYMONTH = array( + $monthly_on_day_bymonthday_of_bymonth = array( '#type' => 'container', '#tree' => TRUE, ); list($bymonthday_title, $bymonthday_suffix) = explode('@bymonthday', t('On day @bymonthday of', array(), array('context' => 'Date repeat'))); - $MONTHLY_on_day_BYMONTHDAY_of_BYMONTH['BYMONTHDAY'] = array( + $monthly_on_day_bymonthday_of_bymonth['BYMONTHDAY'] = array( '#type' => 'select', '#title' => $bymonthday_title, '#default_value' => !empty($rrule['BYMONTHDAY']) && $rrule['FREQ'] === 'MONTHLY' ? $rrule['BYMONTHDAY'] : '', @@ -292,11 +297,11 @@ function _date_repeat_rrule_process($element, &$form_state, $form) { '#field_suffix' => $bymonthday_suffix, ); - $MONTHLY_on_day_BYMONTHDAY_of_BYMONTH['BYMONTH'] = array( + $monthly_on_day_bymonthday_of_bymonth['BYMONTH'] = array( '#type' => 'checkboxes', '#title' => t('Bymonth', array(), array('context' => 'Date repeat')), '#title_display' => 'invisible', - '#default_value' => !empty($rrule['BYMONTH']) && $rrule['FREQ'] === 'MONTHLY' && $MONTHLY_day_month_default === 'BYMONTHDAY_BYMONTH' ? $rrule['BYMONTH'] : array(), + '#default_value' => !empty($rrule['BYMONTH']) && $rrule['FREQ'] === 'MONTHLY' && $monthly_day_month_default === 'BYMONTHDAY_BYMONTH' ? $rrule['BYMONTH'] : array(), '#options' => date_month_names_abbr(TRUE), '#attributes' => array('class' => array('container-inline')), '#multiple' => TRUE, @@ -304,45 +309,45 @@ function _date_repeat_rrule_process($element, &$form_state, $form) { '#suffix' => '', ); - $MONTHLY_on_the_BYDAY_of_BYMONTH = array( + $monthly_on_the_byday_of_bymonth = array( '#type' => 'container', '#tree' => TRUE, ); - $MONTHLY_BYDAY_COUNT = ''; - $MONTHLY_BYDAY_DAY = ''; + $monthly_byday_count = ''; + $monthly_byday_day = ''; if (isset($rrule['BYDAY']) && !empty($rrule['BYDAY']) && $rrule['FREQ'] === 'MONTHLY') { - $MONTHLY_BYDAY_COUNT = substr($rrule['BYDAY'][0], 0, -2); - $MONTHLY_BYDAY_DAY = substr($rrule['BYDAY'][0], -2);; + $monthly_byday_count = substr($rrule['BYDAY'][0], 0, -2); + $monthly_byday_day = substr($rrule['BYDAY'][0], -2);; } list($byday_count_title, $byday_day_title) = explode('@byday', t('On the @byday of', array(), array('context' => 'Date repeat'))); - $MONTHLY_on_the_BYDAY_of_BYMONTH['BYDAY_COUNT'] = array( + $monthly_on_the_byday_of_bymonth['BYDAY_COUNT'] = array( '#type' => 'select', '#title' => $byday_count_title, - '#default_value' => !empty($MONTHLY_BYDAY_COUNT) ? $MONTHLY_BYDAY_COUNT : '', + '#default_value' => !empty($monthly_byday_count) ? $monthly_byday_count : '', '#options' => date_order_translated(), '#multiple' => FALSE, '#prefix' => '
    ', '#suffix' => '
    ', ); - $MONTHLY_on_the_BYDAY_of_BYMONTH['BYDAY_DAY'] = array( + $monthly_on_the_byday_of_bymonth['BYDAY_DAY'] = array( '#type' => 'select', '#title' => $byday_day_title, '#title_display' => 'after', - '#default_value' => !empty($MONTHLY_BYDAY_DAY) ? $MONTHLY_BYDAY_DAY : '', + '#default_value' => !empty($monthly_byday_day) ? $monthly_byday_day : '', '#options' => date_repeat_dow_day_options(TRUE), '#multiple' => FALSE, '#prefix' => '
    ', '#suffix' => '
    ', ); - $MONTHLY_on_the_BYDAY_of_BYMONTH['BYMONTH'] = array( + $monthly_on_the_byday_of_bymonth['BYMONTH'] = array( '#type' => 'checkboxes', '#title' => t('Bymonth', array(), array('context' => 'Date repeat')), '#title_display' => 'invisible', - '#default_value' => !empty($rrule['BYMONTH']) && $rrule['FREQ'] === 'MONTHLY' && $MONTHLY_day_month_default === 'BYDAY_BYMONTH' ? $rrule['BYMONTH'] : array(), + '#default_value' => !empty($rrule['BYMONTH']) && $rrule['FREQ'] === 'MONTHLY' && $monthly_day_month_default === 'BYDAY_BYMONTH' ? $rrule['BYMONTH'] : array(), '#options' => date_month_names_abbr(TRUE), '#attributes' => array('class' => array('container-inline')), '#multiple' => TRUE, @@ -361,31 +366,31 @@ function _date_repeat_rrule_process($element, &$form_state, $form) { ), ), '#attributes' => array('class' => array('date-repeat-radios clearfix')), - '#default_value' => $MONTHLY_day_month_default, + '#default_value' => $monthly_day_month_default, '#options' => array( 'BYMONTHDAY_BYMONTH' => t('On day ... of ...'), 'BYDAY_BYMONTH' => t('On the ... of ...'), ), - 'BYMONTHDAY_BYMONTH_child' => $MONTHLY_on_day_BYMONTHDAY_of_BYMONTH, - 'BYDAY_BYMONTH_child' => $MONTHLY_on_the_BYDAY_of_BYMONTH, + 'BYMONTHDAY_BYMONTH_child' => $monthly_on_day_bymonthday_of_bymonth, + 'BYDAY_BYMONTH_child' => $monthly_on_the_byday_of_bymonth, '#div_classes' => array( 'date-repeat-radios-item date-clear clearfix bymonthday-bymonth', 'date-repeat-radios-item date-clear clearfix byday-bymonth', ), ); - $YEARLY_day_month_default = 'BYMONTHDAY_BYMONTH'; + $yearly_day_month_default = 'BYMONTHDAY_BYMONTH'; if (isset($rrule['FREQ']) && $rrule['FREQ'] === 'YEARLY' && !empty($rrule['BYDAY'])) { - $YEARLY_day_month_default = 'BYDAY_BYMONTH'; + $yearly_day_month_default = 'BYDAY_BYMONTH'; } - $YEARLY_on_day_BYMONTHDAY_of_BYMONTH = array( + $yearly_on_day_bymonthday_of_bymonth = array( '#type' => 'container', '#tree' => TRUE, ); list($bymonthday_title, $bymonthday_suffix) = explode('@bymonthday', t('On day @bymonthday of', array(), array('context' => 'Date repeat'))); - $YEARLY_on_day_BYMONTHDAY_of_BYMONTH['BYMONTHDAY'] = array( + $yearly_on_day_bymonthday_of_bymonth['BYMONTHDAY'] = array( '#type' => 'select', '#title' => $bymonthday_title, '#default_value' => !empty($rrule['BYMONTHDAY']) && $rrule['FREQ'] === 'YEARLY' ? $rrule['BYMONTHDAY'] : '', @@ -396,11 +401,11 @@ function _date_repeat_rrule_process($element, &$form_state, $form) { '#field_suffix' => $bymonthday_suffix, ); - $YEARLY_on_day_BYMONTHDAY_of_BYMONTH['BYMONTH'] = array( + $yearly_on_day_bymonthday_of_bymonth['BYMONTH'] = array( '#type' => 'checkboxes', '#title' => t('Bymonth', array(), array('context' => 'Date repeat')), '#title_display' => 'invisible', - '#default_value' => !empty($rrule['BYMONTH']) && $rrule['FREQ'] === 'YEARLY' && $YEARLY_day_month_default === 'BYMONTHDAY_BYMONTH' ? $rrule['BYMONTH'] : array(), + '#default_value' => !empty($rrule['BYMONTH']) && $rrule['FREQ'] === 'YEARLY' && $yearly_day_month_default === 'BYMONTHDAY_BYMONTH' ? $rrule['BYMONTH'] : array(), '#options' => date_month_names_abbr(TRUE), '#attributes' => array('class' => array('container-inline')), '#multiple' => TRUE, @@ -408,45 +413,45 @@ function _date_repeat_rrule_process($element, &$form_state, $form) { '#suffix' => '', ); - $YEARLY_on_the_BYDAY_of_BYMONTH = array( + $yearly_on_the_byday_of_bymonth = array( '#type' => 'container', '#tree' => TRUE, ); - $YEARLY_BYDAY_COUNT = ''; - $YEARLY_BYDAY_DAY = ''; + $yearly_byday_count = ''; + $yearly_byday_day = ''; if (isset($rrule['BYDAY']) && !empty($rrule['BYDAY']) && $rrule['FREQ'] === 'YEARLY') { - $YEARLY_BYDAY_COUNT = substr($rrule['BYDAY'][0], 0, -2); - $YEARLY_BYDAY_DAY = substr($rrule['BYDAY'][0], -2);; + $yearly_byday_count = substr($rrule['BYDAY'][0], 0, -2); + $yearly_byday_day = substr($rrule['BYDAY'][0], -2);; } list($byday_count_title, $byday_day_title) = explode('@byday', t('On the @byday of', array(), array('context' => 'Date repeat'))); - $YEARLY_on_the_BYDAY_of_BYMONTH['BYDAY_COUNT'] = array( + $yearly_on_the_byday_of_bymonth['BYDAY_COUNT'] = array( '#type' => 'select', '#title' => $byday_count_title, - '#default_value' => !empty($YEARLY_BYDAY_COUNT) ? $YEARLY_BYDAY_COUNT : '', + '#default_value' => !empty($yearly_byday_count) ? $yearly_byday_count : '', '#options' => date_order_translated(), '#multiple' => FALSE, '#prefix' => '
    ', '#suffix' => '
    ', ); - $YEARLY_on_the_BYDAY_of_BYMONTH['BYDAY_DAY'] = array( + $yearly_on_the_byday_of_bymonth['BYDAY_DAY'] = array( '#type' => 'select', '#title' => $byday_day_title, '#title_display' => 'after', - '#default_value' => !empty($YEARLY_BYDAY_DAY) ? $YEARLY_BYDAY_DAY : '', + '#default_value' => !empty($yearly_byday_day) ? $yearly_byday_day : '', '#options' => date_repeat_dow_day_options(TRUE), '#multiple' => FALSE, '#prefix' => '
    ', '#suffix' => '
    ', ); - $YEARLY_on_the_BYDAY_of_BYMONTH['BYMONTH'] = array( + $yearly_on_the_byday_of_bymonth['BYMONTH'] = array( '#type' => 'checkboxes', '#title' => t('Bymonth', array(), array('context' => 'Date repeat')), '#title_display' => 'invisible', - '#default_value' => !empty($rrule['BYMONTH']) && $rrule['FREQ'] === 'YEARLY' && $YEARLY_day_month_default === 'BYDAY_BYMONTH' ? $rrule['BYMONTH'] : array(), + '#default_value' => !empty($rrule['BYMONTH']) && $rrule['FREQ'] === 'YEARLY' && $yearly_day_month_default === 'BYDAY_BYMONTH' ? $rrule['BYMONTH'] : array(), '#options' => date_month_names_abbr(TRUE), '#attributes' => array('class' => array('container-inline')), '#multiple' => TRUE, @@ -465,13 +470,13 @@ function _date_repeat_rrule_process($element, &$form_state, $form) { ), ), '#attributes' => array('class' => array('date-repeat-radios clearfix')), - '#default_value' => $YEARLY_day_month_default, + '#default_value' => $yearly_day_month_default, '#options' => array( 'BYMONTHDAY_BYMONTH' => t('On day ... of ...'), 'BYDAY_BYMONTH' => t('On the ... of ...'), ), - 'BYMONTHDAY_BYMONTH_child' => $YEARLY_on_day_BYMONTHDAY_of_BYMONTH, - 'BYDAY_BYMONTH_child' => $YEARLY_on_the_BYDAY_of_BYMONTH, + 'BYMONTHDAY_BYMONTH_child' => $yearly_on_day_bymonthday_of_bymonth, + 'BYDAY_BYMONTH_child' => $yearly_on_the_byday_of_bymonth, '#div_classes' => array( 'date-repeat-radios-item date-clear clearfix bymonthday-bymonth', 'date-repeat-radios-item date-clear clearfix byday-bymonth', @@ -482,7 +487,7 @@ function _date_repeat_rrule_process($element, &$form_state, $form) { $count_form_element = array( '#type' => 'textfield', '#title' => t('Count', array(), array('context' => 'Date repeat')), - '#default_value' => $COUNT, + '#default_value' => $count, '#element_validate' => array('element_validate_integer_positive'), '#attributes' => array('placeholder' => array('#')), '#prefix' => $prefix, @@ -499,21 +504,26 @@ function _date_repeat_rrule_process($element, &$form_state, $form) { '#type' => $element['#date_repeat_widget'], '#title' => t('Until', array(), array('context' => 'Date repeat')), '#title_display' => 'invisible', - '#default_value' => $UNTIL, - '#date_format' => !empty($element['#date_format']) ? date_limit_format($element['#date_format'], array('year', 'month', 'day')) : 'Y-m-d', + '#default_value' => $until, + '#date_format' => !empty($element['#date_format']) ? + date_limit_format($element['#date_format'], array('year', 'month', 'day')) : 'Y-m-d', '#date_timezone' => $timezone, '#date_text_parts' => !empty($element['#date_text_parts']) ? $element['#date_text_parts'] : array(), '#date_year_range' => !empty($element['#date_year_range']) ? $element['#date_year_range'] : '-3:+3', - '#date_label_position' => !empty($element['#date_label_position']) ? $element['#date_label_position'] : 'within', + '#date_label_position' => !empty($element['#date_label_position']) ? + $element['#date_label_position'] : 'within', '#date_flexible' => 0, ), 'tz' => array('#type' => 'hidden', '#value' => $element['#date_timezone']), 'all_day' => array('#type' => 'hidden', '#value' => 1), - 'granularity' => array('#type' => 'hidden', '#value' => serialize(array('year', 'month', 'day'))), + 'granularity' => array( + '#type' => 'hidden', + '#value' => serialize(array('year', 'month', 'day')), + ), ); $range_of_repeat_default = 'COUNT'; - if (!empty($UNTIL)) { + if (!empty($until)) { $range_of_repeat_default = 'UNTIL'; } $element['range_of_repeat'] = array( @@ -528,7 +538,7 @@ function _date_repeat_rrule_process($element, &$form_state, $form) { ":input[name=\"{$element['#name']}[FREQ]\"]" => array('value' => 'NONE'), ), ), - '#default_value' => $range_of_repeat_default, + '#default_value' => $range_of_repeat_default, '#options' => array( 'COUNT' => t('Count'), 'UNTIL' => t('Until'), @@ -544,7 +554,8 @@ function _date_repeat_rrule_process($element, &$form_state, $form) { $parents = $element['#array_parents']; $instance = implode('-', $parents); - // Make sure this will work right either in the normal form or in an ajax callback from the 'Add more' button. + // Make sure this will work right either in the normal + // form or in an ajax callback from the 'Add more' button. if (empty($form_state['num_exceptions'][$instance])) { $form_state['num_exceptions'][$instance] = count($exceptions); } @@ -576,33 +587,48 @@ function _date_repeat_rrule_process($element, &$form_state, $form) { ), ), ); - for ($i = 0; $i < max($form_state['num_exceptions'][$instance], 1) ; $i++) { - $EXCEPT = ''; + for ($i = 0; $i < max($form_state['num_exceptions'][$instance], 1); $i++) { + $except = ''; if (!empty($exceptions[$i]['datetime'])) { $ex_date = new DateObject($exceptions[$i]['datetime'], $exceptions[$i]['tz']); date_timezone_set($ex_date, timezone_open($timezone)); - $EXCEPT = date_format($ex_date, DATE_FORMAT_DATETIME); + $except = date_format($ex_date, DATE_FORMAT_DATETIME); + } + $date_format = 'Y-m-d'; + if (!empty($element['#date_format'])) { + $grans = array('year', 'month', 'day'); + $date_format = date_limit_format($element['#date_format'], $grans); } $element['exceptions']['EXDATE'][$i] = array( '#tree' => TRUE, 'datetime' => array( '#name' => 'exceptions|' . $instance, '#type' => $element['#date_repeat_widget'], - '#default_value' => $EXCEPT, - '#date_timezone' => !empty($element['#date_timezone']) ? $element['#date_timezone'] : date_default_timezone(), - '#date_format' => !empty($element['#date_format']) ? date_limit_format($element['#date_format'], array('year', 'month', 'day')) : 'Y-m-d', + '#default_value' => $except, + '#date_timezone' => !empty($element['#date_timezone']) ? + $element['#date_timezone'] : date_default_timezone(), + '#date_format' => $date_format, '#date_text_parts' => !empty($element['#date_text_parts']) ? $element['#date_text_parts'] : array(), '#date_year_range' => !empty($element['#date_year_range']) ? $element['#date_year_range'] : '-3:+3', '#date_label_position' => !empty($element['#date_label_position']) ? $element['#date_label_position'] : 'within', '#date_flexible' => 0, - ), - 'tz' => array('#type' => 'hidden', '#value' => $element['#date_timezone']), - 'all_day' => array('#type' => 'hidden', '#value' => 1), - 'granularity' => array('#type' => 'hidden', '#value' => serialize(array('year', 'month', 'day'))), - ); + ), + 'tz' => array( + '#type' => 'hidden', + '#value' => $element['#date_timezone'], + ), + 'all_day' => array( + '#type' => 'hidden', + '#value' => 1, + ), + 'granularity' => array( + '#type' => 'hidden', + '#value' => serialize(array('year', 'month', 'day')), + ), + ); } - // collect additions in the same way as exceptions - implements RDATE. + // Collect additions in the same way as exceptions - implements RDATE. if (empty($form_state['num_additions'][$instance])) { $form_state['num_additions'][$instance] = count($additions); } @@ -634,30 +660,45 @@ function _date_repeat_rrule_process($element, &$form_state, $form) { ), ), ); - for ($i = 0; $i < max($form_state['num_additions'][$instance], 1) ; $i++) { - $RDATE = ''; + for ($i = 0; $i < max($form_state['num_additions'][$instance], 1); $i++) { + $r_date = ''; if (!empty($additions[$i]['datetime'])) { $rdate = new DateObject($additions[$i]['datetime'], $additions[$i]['tz']); date_timezone_set($rdate, timezone_open($timezone)); - $RDATE = date_format($rdate, DATE_FORMAT_DATETIME); + $r_date = date_format($rdate, DATE_FORMAT_DATETIME); + } + $date_format = 'Y-m-d'; + if (!empty($element['#date_format'])) { + $grans = array('year', 'month', 'day'); + $date_format = date_limit_format($element['#date_format'], $grans); } $element['additions']['RDATE'][$i] = array( '#tree' => TRUE, 'datetime' => array( '#type' => $element['#date_repeat_widget'], '#name' => 'additions|' . $instance, - '#default_value' => $RDATE, - '#date_timezone' => !empty($element['#date_timezone']) ? $element['#date_timezone'] : date_default_timezone(), - '#date_format' => !empty($element['#date_format']) ? date_limit_format($element['#date_format'], array('year', 'month', 'day')) : 'Y-m-d', + '#default_value' => $r_date, + '#date_timezone' => !empty($element['#date_timezone']) ? + $element['#date_timezone'] : date_default_timezone(), + '#date_format' => $date_format, '#date_text_parts' => !empty($element['#date_text_parts']) ? $element['#date_text_parts'] : array(), '#date_year_range' => !empty($element['#date_year_range']) ? $element['#date_year_range'] : '-3:+3', '#date_label_position' => !empty($element['#date_label_position']) ? $element['#date_label_position'] : 'within', '#date_flexible' => 0, - ), - 'tz' => array('#type' => 'hidden', '#value' => $element['#date_timezone']), - 'all_day' => array('#type' => 'hidden', '#value' => 1), - 'granularity' => array('#type' => 'hidden', '#value' => serialize(array('year', 'month', 'day'))), - ); + ), + 'tz' => array( + '#type' => 'hidden', + '#value' => $element['#date_timezone'], + ), + 'all_day' => array( + '#type' => 'hidden', + '#value' => 1, + ), + 'granularity' => array( + '#type' => 'hidden', + '#value' => serialize(array('year', 'month', 'day')), + ), + ); } $element['exceptions']['exceptions_add'] = array( @@ -687,6 +728,9 @@ function _date_repeat_rrule_process($element, &$form_state, $form) { return $element; } +/** + * Add callback to date repeat. + */ function date_repeat_add_exception_callback($form, &$form_state) { $parents = $form_state['triggering_element']['#array_parents']; $button_key = array_pop($parents); @@ -694,6 +738,9 @@ function date_repeat_add_exception_callback($form, &$form_state) { return $element; } +/** + * Add addition callback to date repeat. + */ function date_repeat_add_addition_callback($form, &$form_state) { $parents = $form_state['triggering_element']['#array_parents']; $button_key = array_pop($parents); @@ -701,6 +748,9 @@ function date_repeat_add_addition_callback($form, &$form_state) { return $element; } +/** + * Add exception to date repeat. + */ function date_repeat_add_exception($form, &$form_state) { $parents = $form_state['triggering_element']['#array_parents']; $instance = implode('-', array_slice($parents, 0, count($parents) - 2)); @@ -708,6 +758,9 @@ function date_repeat_add_exception($form, &$form_state) { $form_state['rebuild'] = TRUE; } +/** + * Add addition to date repeat. + */ function date_repeat_add_addition($form, &$form_state) { $parents = $form_state['triggering_element']['#array_parents']; $instance = implode('-', array_slice($parents, 0, count($parents) - 2)); @@ -723,8 +776,14 @@ function date_repeat_merge($form_values, $element) { return $form_values; } if (array_key_exists('exceptions', $form_values) || array_key_exists('additions', $form_values)) { - if (!array_key_exists('exceptions', $form_values)) $form_values['exceptions'] = array(); - if (!array_key_exists('additions', $form_values)) $form_values['additions'] = array(); + if (!array_key_exists('exceptions', $form_values)) { + $form_values['exceptions'] = array(); + } + + if (!array_key_exists('additions', $form_values)) { + $form_values['additions'] = array(); + } + $form_values = array_merge($form_values, (array) $form_values['exceptions'], (array) $form_values['additions']); unset($form_values['exceptions']); unset($form_values['additions']); @@ -738,18 +797,22 @@ function date_repeat_merge($form_values, $element) { case 'INTERVAL': $form_values['INTERVAL'] = $form_values['daily']['INTERVAL_child']; break; + case 'every_weekday': $form_values['BYDAY'] = array('MO', 'TU', 'WE', 'TH', 'FR'); break; + case 'every_mo_we_fr': $form_values['BYDAY'] = array('MO', 'WE', 'FR'); break; + case 'every_tu_th': $form_values['BYDAY'] = array('TU', 'TH'); break; } } break; + case 'WEEKLY': if (array_key_exists('weekly', $form_values)) { $form_values = array_merge($form_values, (array) $form_values['weekly']); @@ -758,12 +821,14 @@ function date_repeat_merge($form_values, $element) { } } break; + case 'MONTHLY': if (array_key_exists('monthly', $form_values)) { switch ($form_values['monthly']['day_month']) { case 'BYMONTHDAY_BYMONTH': $form_values['monthly'] = array_merge($form_values['monthly'], (array) $form_values['monthly']['BYMONTHDAY_BYMONTH_child']); break; + case 'BYDAY_BYMONTH': $form_values['monthly']['BYDAY_BYMONTH_child']['BYDAY'] = $form_values['monthly']['BYDAY_BYMONTH_child']['BYDAY_COUNT'] . $form_values['monthly']['BYDAY_BYMONTH_child']['BYDAY_DAY']; $form_values['monthly'] = array_merge($form_values['monthly'], (array) $form_values['monthly']['BYDAY_BYMONTH_child']); @@ -783,12 +848,14 @@ function date_repeat_merge($form_values, $element) { } } break; + case 'YEARLY': if (array_key_exists('yearly', $form_values)) { switch ($form_values['yearly']['day_month']) { case 'BYMONTHDAY_BYMONTH': $form_values['yearly'] = array_merge($form_values['yearly'], (array) $form_values['yearly']['BYMONTHDAY_BYMONTH_child']); break; + case 'BYDAY_BYMONTH': $form_values['yearly']['BYDAY_BYMONTH_child']['BYDAY'] = $form_values['yearly']['BYDAY_BYMONTH_child']['BYDAY_COUNT'] . $form_values['yearly']['BYDAY_BYMONTH_child']['BYDAY_DAY']; $form_values['yearly'] = array_merge($form_values['yearly'], (array) $form_values['yearly']['BYDAY_BYMONTH_child']); @@ -808,6 +875,7 @@ function date_repeat_merge($form_values, $element) { } } break; + default: break; } @@ -823,6 +891,7 @@ function date_repeat_merge($form_values, $element) { case 'COUNT': $form_values['COUNT'] = $form_values['count_child']; break; + case 'UNTIL': $form_values['UNTIL'] = $form_values['until_child']; break; @@ -832,14 +901,23 @@ function date_repeat_merge($form_values, $element) { unset($form_values['count_child']); unset($form_values['until_child']); - if (array_key_exists('BYDAY', $form_values) && is_array($form_values['BYDAY'])) unset($form_values['BYDAY']['']); - if (array_key_exists('BYMONTH', $form_values) && is_array($form_values['BYMONTH'])) unset($form_values['BYMONTH']['']); - if (array_key_exists('BYMONTHDAY', $form_values) && is_array($form_values['BYMONTHDAY'])) unset($form_values['BYMONTHDAY']['']); + if (array_key_exists('BYDAY', $form_values) && is_array($form_values['BYDAY'])) { + unset($form_values['BYDAY']['']); + } + + if (array_key_exists('BYMONTH', $form_values) && is_array($form_values['BYMONTH'])) { + unset($form_values['BYMONTH']['']); + } + + if (array_key_exists('BYMONTHDAY', $form_values) && is_array($form_values['BYMONTHDAY'])) { + unset($form_values['BYMONTHDAY']['']); + } if (array_key_exists('UNTIL', $form_values) && is_array($form_values['UNTIL']['datetime'])) { $function = $element['#date_repeat_widget'] . '_input_date'; $until_element = $element; - $until_element['#date_format'] = !empty($element['#date_format']) ? date_limit_format($element['#date_format'], array('year', 'month', 'day')) : 'Y-m-d'; + $until_element['#date_format'] = !empty($element['#date_format']) ? + date_limit_format($element['#date_format'], array('year', 'month', 'day')) : 'Y-m-d'; $date = $function($until_element, $form_values['UNTIL']['datetime']); $form_values['UNTIL']['datetime'] = is_object($date) ? $date->format(DATE_FORMAT_DATETIME) : ''; } @@ -849,9 +927,14 @@ function date_repeat_merge($form_values, $element) { if (array_key_exists('EXDATE', $form_values) && is_array($form_values['EXDATE'])) { $function = $element['#date_repeat_widget'] . '_input_date'; $exdate_element = $element; + $date_format = 'Y-m-d'; + if (!empty($element['#date_format'])) { + $grans = array('year', 'month', 'day'); + $date_format = date_limit_format($element['#date_format'], $grans); + } foreach ($form_values['EXDATE'] as $delta => $value) { if (is_array($value['datetime'])) { - $exdate_element['#date_format'] = !empty($element['#date_format']) ? date_limit_format($element['#date_format'], array('year', 'month', 'day')) : 'Y-m-d'; + $exdate_element['#date_format'] = $date_format; $date = $function($exdate_element, $form_values['EXDATE'][$delta]['datetime']); $form_values['EXDATE'][$delta]['datetime'] = is_object($date) ? $date->format(DATE_FORMAT_DATETIME) : ''; } @@ -864,9 +947,14 @@ function date_repeat_merge($form_values, $element) { if (array_key_exists('RDATE', $form_values) && is_array($form_values['RDATE'])) { $function = $element['#date_repeat_widget'] . '_input_date'; $rdate_element = $element; + $date_format = 'Y-m-d'; + if (!empty($element['#date_format'])) { + $grans = array('year', 'month', 'day'); + $date_format = date_limit_format($element['#date_format'], $grans); + } foreach ($form_values['RDATE'] as $delta => $value) { if (is_array($value['datetime'])) { - $rdate_element['#date_format'] = !empty($element['#date_format']) ? date_limit_format($element['#date_format'], array('year', 'month', 'day')) : 'Y-m-d'; + $rdate_element['#date_format'] = $date_format; $date = $function($rdate_element, $form_values['RDATE'][$delta]['datetime']); $form_values['RDATE'][$delta]['datetime'] = is_object($date) ? $date->format(DATE_FORMAT_DATETIME) : ''; } @@ -910,7 +998,7 @@ function date_repeat_rrule_validate($element, &$form_state) { } /** - * Theme the exception list as a table so the buttons line up + * Theme the exception list as a table so the buttons line up. */ function theme_date_repeat_current_exceptions($vars) { $rows = $vars['rows']; @@ -920,11 +1008,14 @@ function theme_date_repeat_current_exceptions($vars) { $rows_info[] = array(drupal_render($value['action']), drupal_render($value['display'])); } } - return theme('table', array('header' => array(t('Delete'), t('Current exceptions')), 'rows' => $rows_info)); + return theme('table', array( + 'header' => array(t('Delete'), t('Current exceptions')), + 'rows' => $rows_info) + ); } - /** - * Theme the exception list as a table so the buttons line up +/** + * Theme the exception list as a table so the buttons line up. */ function theme_date_repeat_current_additions($rows = array()) { $rows_info = array(); @@ -933,7 +1024,10 @@ function theme_date_repeat_current_additions($rows = array()) { $rows_info[] = array(drupal_render($value['action']), drupal_render($value['display'])); } } - return theme('table', array('header' => array(t('Delete'), t('Current additions')), 'rows' => $rows_info)); + return theme('table', array( + 'header' => array(t('Delete'), t('Current additions')), + 'rows' => $rows_info) + ); } /** @@ -941,19 +1035,23 @@ function theme_date_repeat_current_additions($rows = array()) { */ function theme_date_repeat_rrule($vars) { $element = $vars['element']; - $class = $element['#date_repeat_collapsed'] ? array('date-no-float', 'collapsible', 'collapsed') : array('date-no-float', 'collapsible'); $id = drupal_html_id('repeat-settings-fieldset'); $parents = $element['#parents']; - $selector = "{$parents[0]}[{$parents[1]}][{$parents[2]}][show_repeat_settings]"; + + $selector = $parents[0]; + for ($i = 1; $i < count($parents) - 1; $i++) { + $selector .= '[' . $parents[$i] . ']'; + } + $selector .= '[show_repeat_settings]'; + $fieldset = array( '#type' => 'item', '#title' => t('Repeat settings'), '#title_display' => 'invisible', - '#attributes' => array('class' => $class), '#markup' => $element['#children'], '#states' => array( - 'visible' => array( - ":input[name=\"{$selector}\"]" => array('checked' => TRUE), + 'invisible' => array( + ":input[name=\"{$selector}\"]" => array('checked' => FALSE), ), ), '#id' => $id, @@ -962,6 +1060,9 @@ function theme_date_repeat_rrule($vars) { return drupal_render($fieldset); } +/** + * Filter non zero values. + */ function date_repeat_filter_non_zero_value($value) { return $value !== 0; } diff --git a/sites/all/modules/contrib/fields/date/date_repeat/tests/date_repeat.test b/sites/all/modules/contrib/fields/date/date_repeat/tests/date_repeat.test index fd46e81..fc4e5c3 100644 --- a/sites/all/modules/contrib/fields/date/date_repeat/tests/date_repeat.test +++ b/sites/all/modules/contrib/fields/date/date_repeat/tests/date_repeat.test @@ -377,6 +377,19 @@ class DateRepeatTestCase extends DrupalWebTestCase { $result = implode(', ', $dates); $this->assertEqual($result, $shouldbe, $rule . '; Starting ' . $start . '; results: ' . $result); + //Every Last Thursday in November, every year, five times: + $start = "2014-11-27 00:00:00"; + $rule = 'FREQ=YEARLY;INTERVAL=1;BYDAY=-1TH;BYMONTH=11;COUNT=5;WKST=SU'; + // ==> (2014 00:00 AM EDT)November 27 + // (2015 00:00 AM EDT)November 26 + // (2016 00:00 AM EDT)November 24 + // (2017 00:00 AM EDT)November 30 + // (2018 00:00 AM EDT)November 29 + $dates = date_repeat_calc($rule, $start, NULL, array()); + $shouldbe = '2014-11-27 00:00:00, 2015-11-26 00:00:00, 2016-11-24 00:00:00, 2017-11-30 00:00:00, 2018-11-29 00:00:00'; + $result = implode(', ', $dates); + $this->assertEqual($result, $shouldbe, $rule . '; Starting ' . $start . '; results: ' . $result); + return; //Every Thanksgiving, forever: diff --git a/sites/all/modules/contrib/fields/date/date_repeat/tests/date_repeat_form.test b/sites/all/modules/contrib/fields/date/date_repeat/tests/date_repeat_form.test index d514739..22d6529 100644 --- a/sites/all/modules/contrib/fields/date/date_repeat/tests/date_repeat_form.test +++ b/sites/all/modules/contrib/fields/date/date_repeat/tests/date_repeat_form.test @@ -25,7 +25,7 @@ class DateRepeatFormTestCase extends DrupalWebTestCase { // Create and log in our privileged user. $this->privileged_user = $this->drupalCreateUser(array( - 'administer content types', 'administer nodes', 'bypass node access', 'view date repeats' + 'administer content types', 'administer nodes', 'bypass node access', 'view date repeats', 'administer fields' )); $this->drupalLogin($this->privileged_user); @@ -195,20 +195,22 @@ class DateRepeatFormTestCase extends DrupalWebTestCase { $edit = array(); $edit['title'] = $this->randomName(8); $edit['body[und][0][value]'] = $this->randomName(16); + $current_year = date('Y'); + switch ($options) { case 'select': - $edit['field_test[und][0][value][year]'] = '2010'; + $edit['field_test[und][0][value][year]'] = $current_year; $edit['field_test[und][0][value][month]'] = '10'; $edit['field_test[und][0][value][day]'] = '7'; $edit['field_test[und][0][value][hour]'] = '10'; $edit['field_test[und][0][value][minute]'] = '30'; break; case 'text': - $edit['field_test[und][0][value][date]'] = '2010-10-07 10:30'; + $edit['field_test[und][0][value][date]'] = format_string('!year-10-07 10:30', array('!year' => $current_year)); break; case 'popup': - $edit['field_test[und][0][value][date]'] = '2010-10-07'; + $edit['field_test[und][0][value][date]'] = format_string('!year-10-07', array('!year' => $current_year)); $edit['field_test[und][0][value][time]'] = '10:30'; break; } @@ -378,7 +380,7 @@ class DateRepeatFormTestCase extends DrupalWebTestCase { else { $edit['field_test[und][0][rrule][range_of_repeat]'] = 'UNTIL'; $date = array( - 'year' => '2011', + 'year' => $current_year + 1, 'month' => '10', 'day' => '07' ); @@ -392,7 +394,7 @@ class DateRepeatFormTestCase extends DrupalWebTestCase { case 'exclude': $exclude_include_edit['field_test[und][0][rrule][show_exceptions]'] = TRUE; $date = array( - 'year' => '2010', + 'year' => $current_year, 'month' => '10', 'day' => '07' ); @@ -401,7 +403,7 @@ class DateRepeatFormTestCase extends DrupalWebTestCase { case 'include': $exclude_include_edit['field_test[und][0][rrule][show_additions]'] = TRUE; $date = array( - 'year' => '2013', + 'year' => $current_year + 3, 'month' => '10', 'day' => '07' ); @@ -410,7 +412,7 @@ class DateRepeatFormTestCase extends DrupalWebTestCase { case 'exclude_include': $exclude_include_edit['field_test[und][0][rrule][show_exceptions]'] = TRUE; $date = array( - 'year' => '2010', + 'year' => $current_year, 'month' => '10', 'day' => '07' ); @@ -418,7 +420,7 @@ class DateRepeatFormTestCase extends DrupalWebTestCase { $exclude_include_edit['field_test[und][0][rrule][show_additions]'] = TRUE; $date = array( - 'year' => '2013', + 'year' => $current_year + 3, 'month' => '10', 'day' => '07' ); @@ -510,7 +512,6 @@ class DateRepeatFormTestCase extends DrupalWebTestCase { break; case 'text': case 'popup': - //$return["{$field_name}[datetime][date]"] = '2011-10-07'; $return["{$form_field_name}[datetime][date]"] = "{$date['year']}-{$date['month']}-{$date['day']}"; break; } diff --git a/sites/all/modules/contrib/fields/date/date_repeat_field/date_repeat_field.devel_generate.inc b/sites/all/modules/contrib/fields/date/date_repeat_field/date_repeat_field.devel_generate.inc index 3880e8b..51c3708 100644 --- a/sites/all/modules/contrib/fields/date/date_repeat_field/date_repeat_field.devel_generate.inc +++ b/sites/all/modules/contrib/fields/date/date_repeat_field/date_repeat_field.devel_generate.inc @@ -1,5 +1,5 @@ ' . $output . ''; + $output = '
    ' . $output . '
    '; } return $output; } @@ -77,13 +77,13 @@ function date_repeat_field_menu() { $path = field_collection_field_get_path($field); $count = count(explode('/', $path)); $items[$path . '/%field_collection_item/repeats'] = array( - 'title' => 'Repeats', - 'page callback' => 'date_repeat_field_page', - 'page arguments' => array($entity_type, $count), - 'access callback' => 'date_repeat_field_show', - 'access arguments' => array($entity_type, $count), - 'type' => MENU_LOCAL_TASK, - 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE, + 'title' => 'Repeats', + 'page callback' => 'date_repeat_field_page', + 'page arguments' => array($entity_type, $count), + 'access callback' => 'date_repeat_field_show', + 'access arguments' => array($entity_type, $count), + 'type' => MENU_LOCAL_TASK, + 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE, ); } } @@ -91,13 +91,13 @@ function date_repeat_field_menu() { else { $path = $entity_type . '/%' . $entity_type; $items[$path . '/repeats'] = array( - 'title' => 'Repeats', - 'page callback' => 'date_repeat_field_page', - 'page arguments' => array($entity_type, 1), - 'access callback' => 'date_repeat_field_show', - 'access arguments' => array($entity_type, 1), - 'type' => MENU_LOCAL_TASK, - 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE, + 'title' => 'Repeats', + 'page callback' => 'date_repeat_field_page', + 'page arguments' => array($entity_type, 1), + 'access callback' => 'date_repeat_field_show', + 'access arguments' => array($entity_type, 1), + 'type' => MENU_LOCAL_TASK, + 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE, ); } } @@ -108,23 +108,47 @@ function date_repeat_field_menu() { * Implements hook_permission(). */ function date_repeat_field_permission() { - return array('view date repeats' => array( - 'title' => t('View Repeating Dates'), - 'description' => t('Allow user to see a page with all the times a date repeats.'), - )); + return array( + 'view date repeats' => array( + 'title' => t('View Repeating Dates'), + 'description' => t('Allow user to see a page with all the times a date repeats.'), + ), + ); } /** - * See if the user can access repeat date info for this field. + * See if the user can access repeat date info for this entity. + * + * @param string $entity_type + * The entity type. + * @param string $entity + * The specific entity to check (optional). + * + * @return bool + * Return TRUE if there is at least one date field attached to this entity, + * and the current user has the permission 'view date repeats'; FALSE otherwise. */ function date_repeat_field_show($entity_type = 'node', $entity = NULL) { + if (!user_access('view date repeats')) { + return FALSE; + } + $bundle = date_get_entity_bundle($entity_type, $entity); - foreach (field_info_fields() as $field_name => $field) { - if (in_array($field['type'], array('date', 'datestamp', 'datetime')) - && array_key_exists($entity_type, $field['bundles']) - && in_array($bundle, $field['bundles'][$entity_type]) - && date_is_repeat_field($field)) { - return user_access('view date repeats'); + + // In Drupal 7.22 the field_info_field_map() function was added, which is more + // memory-efficient in certain cases than field_info_fields(). + // @see https://drupal.org/node/1915646 + $field_map_available = version_compare(VERSION, '7.22', '>='); + $field_list = $field_map_available ? field_info_field_map() : field_info_fields(); + + foreach ($field_list as $field_name => $data) { + if (in_array($data['type'], array('date', 'datestamp', 'datetime')) + && array_key_exists($entity_type, $data['bundles']) + && in_array($bundle, $data['bundles'][$entity_type])) { + $field_info = $field_map_available ? field_info_field($field_name) : $data; + if (date_is_repeat_field($field_info)) { + return TRUE; + } } } return FALSE; @@ -173,6 +197,9 @@ function date_repeat_field_bundles() { return $values; } +/** + * Check field is repeat. + */ function date_is_repeat_field($field, $instance = NULL) { if (is_string($field)) { $field = field_info_field($field); @@ -193,7 +220,7 @@ function date_is_repeat_field($field, $instance = NULL) { } } -/* +/** * Implements hook_date_field_insert_alter(). */ function date_repeat_field_date_field_insert_alter(&$items, $context) { @@ -217,7 +244,7 @@ function date_repeat_field_date_field_insert_alter(&$items, $context) { } } -/* +/** * Implements hook_date_field_update_alter(). */ function date_repeat_field_date_field_update_alter(&$items, $context) { @@ -257,6 +284,13 @@ function date_repeat_field_field_widget_form_alter(&$element, &$form_state, $con '#suffix' => '', '#default_value' => isset($items[$delta]['rrule']) && !empty($items[$delta]['rrule']) ? 1 : 0, ); + + // Make changes if instance is set to be rendered as a regular field. + if (!empty($instance['widget']['settings']['no_fieldset'])) { + $element['#title'] = check_plain($instance['label']); + $element['#description'] = field_filter_xss($instance['description']); + $element['#theme_wrappers'] = array('date_form_element'); + } } } } @@ -318,13 +352,14 @@ function date_repeat_field_widget_validate($element, &$form_state) { // The RRULE has already been created by this point, so go back // to the posted values to see if this was filled out. $error_field_base = implode('][', $element['#parents']); - $error_field_until = $error_field_base . '][rrule][until_child][datetime]['; + $error_field_until = $error_field_base . '][rrule][until_child][datetime]['; if (!empty($item['rrule']) && $rrule_values['range_of_repeat'] === 'UNTIL' && empty($rrule_values['UNTIL']['datetime'])) { switch ($instance['widget']['type']) { case 'date_text': case 'date_popup': form_set_error($error_field_until . 'date', t("Missing value in 'Range of repeat'. (UNTIL).", array(), array('context' => 'Date repeat'))); break; + case 'date_select': form_set_error($error_field_until . 'year', t("Missing value in 'Range of repeat': Year (UNTIL)", array(), array('context' => 'Date repeat'))); form_set_error($error_field_until . 'month', t("Missing value in 'Range of repeat': Month (UNTIL)", array(), array('context' => 'Date repeat'))); @@ -346,15 +381,8 @@ function date_repeat_field_widget_validate($element, &$form_state) { // the repeating dates, wipe out the previous values, and populate the // field with the new values. - // TODO - // Is it right to not do anything unless there are changes? Will that - // confuse anyone? Commenting that out for now... $rrule = $item['rrule']; - if (!empty($rrule) - //&& ($rrule != $element['rrule']['#prev_rrule'] - //|| $item['value'] != $element['rrule']['#prev_value'] - //|| $item['value2'] != $element['rrule']['#prev_value2']) - ) { + if (!empty($rrule)) { // Avoid undefined index problems on dates that don't have all parts. $possible_items = array('value', 'value2', 'timezone', 'offset', 'offset2'); @@ -367,8 +395,9 @@ function date_repeat_field_widget_validate($element, &$form_state) { // We only collect a date for UNTIL, but we need it to be inclusive, // so force it to a full datetime element at the last possible second of the day. if (!empty($rrule_values['UNTIL'])) { + $gran = array('year', 'month', 'day', 'hour', 'minute', 'second'); $rrule_values['UNTIL']['datetime'] .= ' 23:59:59'; - $rrule_values['UNTIL']['granularity'] = serialize(drupal_map_assoc(array('year', 'month', 'day', 'hour', 'minute', 'second'))); + $rrule_values['UNTIL']['granularity'] = serialize(drupal_map_assoc($gran)); $rrule_values['UNTIL']['all_day'] = 0; } $value = date_repeat_build_dates($rrule, $rrule_values, $field, $item); @@ -403,9 +432,10 @@ function date_repeat_after_build(&$element, &$form_state) { * Pass in either the RRULE or the $form_values array for the RRULE, * whichever is missing will be created when needed. */ +// @codingStandardsIgnoreStart function date_repeat_build_dates($rrule = NULL, $rrule_values = NULL, $field, $item) { - - include_once(DRUPAL_ROOT . '/' . drupal_get_path('module', 'date_api') . '/date_api_ical.inc'); +// @codingStandardsIgnoreEnd + include_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'date_api') . '/date_api_ical.inc'; $field_name = $field['field_name']; if (empty($rrule)) { @@ -482,8 +512,9 @@ function date_repeat_build_dates($rrule = NULL, $rrule_values = NULL, $field, $i 'offset2' => date_offset_get($date_end), 'timezone' => $timezone, 'rrule' => $rrule, - ); + ); } + return $value; } @@ -514,9 +545,6 @@ function date_repeat_field_date_combo_process_alter(&$element, &$form_state, $co '#date_increment' => $instance['widget']['settings']['increment'], '#date_year_range' => $instance['widget']['settings']['year_range'], '#date_label_position' => $instance['widget']['settings']['label_position'], - '#prev_value' => isset($item['value']) ? $item['value'] : '', - '#prev_value2' => isset($item['value2']) ? $item['value2'] : '', - '#prev_rrule' => isset($item['rrule']) ? $item['rrule'] : '', '#date_repeat_widget' => str_replace('_repeat', '', $instance['widget']['type']), '#date_repeat_collapsed' => $instance['widget']['settings']['repeat_collapsed'], '#date_flexible' => 0, @@ -626,6 +654,17 @@ function date_repeat_field_form_field_ui_field_edit_form_alter(&$form, &$form_st $form['field']['cardinality']['#disabled'] = TRUE; $form['field']['cardinality']['#value'] = FIELD_CARDINALITY_UNLIMITED; } + // Repeating dates need unlimited values, confirm that in element_validate. + $form['field']['#element_validate'] = array('date_repeat_field_set_cardinality'); +} + +/** + * Ensure the cardinality gets updated if the option to make a date repeating is checked. + */ +function date_repeat_field_set_cardinality($element, &$form_state) { + if (!empty($form_state['values']['field']['settings']['repeat'])) { + form_set_value($element['cardinality'], FIELD_CARDINALITY_UNLIMITED, $form_state); + } } /** @@ -658,9 +697,8 @@ function date_repeat_field_date_field_widget_settings_form_alter(&$form, $contex '#title' => t('Repeat display', array(), array('context' => 'Date repeat')), '#description' => t("Should the repeat options form start out expanded or collapsed? Set to 'Collapsed' to make those options less obtrusive.", array(), array('context' => 'Date repeat')), '#fieldset' => 'date_format', - ); + ); } - } /** diff --git a/sites/all/modules/contrib/fields/date/date_tools/date_tools.change_type.inc b/sites/all/modules/contrib/fields/date/date_tools/date_tools.change_type.inc index cad2caf..041c5de 100644 --- a/sites/all/modules/contrib/fields/date/date_tools/date_tools.change_type.inc +++ b/sites/all/modules/contrib/fields/date/date_tools/date_tools.change_type.inc @@ -28,10 +28,14 @@ function date_tools_change_type_form() { // Get the available date fields. foreach ($fields as $field_name => $field) { if ($field['type'] == 'date' || $field['type'] == 'datestamp' || $field['type'] == 'datetime') { - $date_options[$labels[$field['type']]][$field_name] = t('Field @label (@field_name)', array('@label' => $field['widget']['label'], '@field_name' => $field_name, '@type' => $labels[$field['type']])); + $date_options[$labels[$field['type']]][$field_name] = t('Field @label (@field_name)', array( + '@label' => $field['widget']['label'], + '@field_name' => $field_name, + '@type' => $labels[$field['type']] + )); } } - if (sizeof($date_options) < 1) { + if (count($date_options) < 1) { drupal_set_message(t('There are no date fields in this database.')); return $form; } @@ -142,26 +146,31 @@ function date_tools_change_type_form_submit($form, &$form_state) { case 'datestamp': $new_columns[] = $date_handler->sql_format('U', $db_field) . ' AS ' . $info['column']; break; + case 'datetime': $new_columns[] = $date_handler->sql_format('Y-m-d H:i:s', $db_field) . ' AS ' . $info['column']; break; } break; + case 'datestamp': switch ($new_type) { case 'date': $new_columns[] = $date_handler->sql_format('Y-m-d/TH:i:s', $db_field) . ' AS ' . $info['column']; break; + case 'datetime': $new_columns[] = $date_handler->sql_format('Y-m-d H:i:s', $db_field) . ' AS ' . $info['column']; break; } break; + case 'datetime': switch ($new_type) { case 'date': $new_columns[] = $date_handler->sql_format('Y-m-d/TH:i:s', $db_field) . ' AS ' . $info['column']; break; + case 'datestamp': $new_columns[] = $date_handler->sql_format('U', $db_field) . ' AS ' . $info['column']; break; @@ -178,5 +187,9 @@ function date_tools_change_type_form_submit($form, &$form_state) { db_query($sql); db_query("DROP TABLE {" . $temp_table . "}"); - drupal_set_message(t('The field @field_name has been changed from @old_type to @new_type.', array('@field_name' => $field['widget']['label'], '@old_type' => $labels[$old_type], '@new_type' => $labels[$new_type]))); + drupal_set_message(t('The field @field_name has been changed from @old_type to @new_type.', array( + '@field_name' => $field['widget']['label'], + '@old_type' => $labels[$old_type], + '@new_type' => $labels[$new_type] + ))); } diff --git a/sites/all/modules/contrib/fields/date/date_tools/date_tools.info b/sites/all/modules/contrib/fields/date/date_tools/date_tools.info index 3f392a4..04bfb03 100644 --- a/sites/all/modules/contrib/fields/date/date_tools/date_tools.info +++ b/sites/all/modules/contrib/fields/date/date_tools/date_tools.info @@ -6,9 +6,9 @@ core = 7.x configure = admin/config/date/tools files[] = tests/date_tools.test -; Information added by drupal.org packaging script on 2012-08-13 -version = "7.x-2.6" +; Information added by Drupal.org packaging script on 2017-04-07 +version = "7.x-2.10" core = "7.x" project = "date" -datestamp = "1344850024" +datestamp = "1491562090" diff --git a/sites/all/modules/contrib/fields/date/date_tools/date_tools.module b/sites/all/modules/contrib/fields/date/date_tools/date_tools.module index a7ac5f1..997808d 100644 --- a/sites/all/modules/contrib/fields/date/date_tools/date_tools.module +++ b/sites/all/modules/contrib/fields/date/date_tools/date_tools.module @@ -31,7 +31,7 @@ function date_tools_help($section, $arg) { */ function date_tools_permission() { return array( - 'administer date tools' => array( + 'administer date tools' => array( 'title' => t('Administer date tools'), ), ); @@ -68,6 +68,7 @@ function date_tools_menu() { 'file' => 'date_tools.wizard.inc', ); + // @codingStandardsIgnoreStart /** $items['admin/config/date/tools/change'] = array( 'title' => 'Change type', @@ -79,18 +80,18 @@ function date_tools_menu() { 'file' => 'date_tools.change_type.inc', ); */ + // @codingStandardsIgnoreEnd return $items; } /** - * Main Date Tools page + * Main Date Tools page. */ function date_tools_page() { $content = ''; - $content .= t('Dates and calendars can be complicated to set up. The !date_wizard makes it easy to create a simple date content type and related calendar. ', array('!date_wizard' => l(t('Date wizard'), 'admin/config/date/tools/date_wizard'))); - + $content .= t('Dates and calendars can be complicated to set up. The !date_wizard makes it easy to create a simple date content type and related calendar.', array('!date_wizard' => l(t('Date wizard'), 'admin/config/date/tools/date_wizard'))); return $content; } diff --git a/sites/all/modules/contrib/fields/date/date_tools/date_tools.wizard.inc b/sites/all/modules/contrib/fields/date/date_tools/date_tools.wizard.inc index 14bc275..94a130a 100644 --- a/sites/all/modules/contrib/fields/date/date_tools/date_tools.wizard.inc +++ b/sites/all/modules/contrib/fields/date/date_tools/date_tools.wizard.inc @@ -6,6 +6,8 @@ */ /** + * Implements hook_form(). + * * @todo. */ function date_tools_wizard_form() { @@ -59,7 +61,10 @@ function date_tools_wizard_form() { $form['field']['repeat'] = array( '#type' => 'select', '#default_value' => 0, - '#options' => array(0 => t('No'), 1 => t('Yes')), + '#options' => array( + 0 => t('No'), + 1 => t('Yes'), + ), '#title' => t('Show repeating date options'), '#access' => module_exists('date_repeat_field'), ); @@ -72,7 +77,11 @@ function date_tools_wizard_form() { $form['field']['advanced']['todate'] = array( '#type' => 'select', '#default_value' => 'optional', - '#options' => array('' => t('Never'), 'optional' => t('Optional'), 'required' => t('Required')), + '#options' => array( + '' => t('Never'), + 'optional' => t('Optional'), + 'required' => t('Required'), + ), '#title' => t('End Date'), '#description' => t("Display a matching second date field as a 'End date'."), ); @@ -106,7 +115,10 @@ function date_tools_wizard_form() { $form['calendar'] = array( '#type' => 'select', '#default_value' => module_exists('calendar'), - '#options' => array(0 => t('No'), 1 => t('Yes')), + '#options' => array( + 0 => t('No'), + 1 => t('Yes'), + ), '#title' => t('Create a calendar for this date field'), '#access' => module_exists('calendar'), ); @@ -119,13 +131,26 @@ function date_tools_wizard_form() { } /** + * Form validate. + * * @todo. */ function date_tools_wizard_form_validate(&$form, &$form_state) { $bundle = $form_state['values']['bundle']; $field_name = 'field_' . $form_state['values']['field_name']; - $existing_type = db_query("SELECT type FROM {node_type} WHERE type=:bundle", array(':bundle' => $bundle))->fetchField(); - $existing_instance = db_query("SELECT field_name FROM {field_config_instance} WHERE field_name=:field_name AND bundle=:bundle AND entity_type=:entity_type", array(':field_name' => $field_name, ':bundle' => $bundle, ':entity_type' => 'node'))->fetchField(); + + $args = array( + ':field_name' => $field_name, + ':bundle' => $bundle, + ':entity_type' => 'node', + ); + + $query = "SELECT type FROM {node_type} WHERE type=:bundle"; + $existing_type = db_query($query, array(':bundle' => $args[':bundle']))->fetchField(); + + $query = "SELECT field_name FROM {field_config_instance} WHERE field_name=:field_name AND bundle=:bundle AND entity_type=:entity_type"; + $existing_instance = db_query($query, $args)->fetchField(); + if ($existing_type) { drupal_set_message(t('This content type name already exists, adding new field to existing content type.')); } @@ -147,6 +172,8 @@ function date_tools_wizard_form_validate(&$form, &$form_state) { } /** + * Form submit. + * * @todo. */ function date_tools_wizard_form_submit(&$form, &$form_state) { @@ -161,6 +188,8 @@ function date_tools_wizard_form_submit(&$form, &$form_state) { } /** + * Wizard build. + * * @todo. */ function date_tools_wizard_build($form_values) { @@ -201,7 +230,7 @@ function date_tools_wizard_build($form_values) { 'timezone_db' => date_get_timezone_db($tz_handling), 'repeat' => $repeat, 'todate' => !empty($todate) ? $todate : 'optional', - ), + ), ); $instance = array( 'entity_type' => 'node', @@ -275,6 +304,8 @@ function date_tools_wizard_build($form_values) { } /** + * Includes handler. + * * @todo. */ function date_tools_wizard_include() { @@ -285,6 +316,8 @@ function date_tools_wizard_include() { } /** + * Implements hook_field_types(). + * * @todo. */ function date_tools_wizard_field_types() { @@ -296,6 +329,7 @@ function date_tools_wizard_field_types() { } /** + * Implements hook_widget_types(). * @todo. */ function date_tools_wizard_widget_types() { @@ -309,6 +343,8 @@ function date_tools_wizard_widget_types() { } /** + * Tz handler. + * * @todo. */ function date_tools_wizard_tz_handling() { @@ -317,6 +353,8 @@ function date_tools_wizard_tz_handling() { } /** + * Create date tools wizard content type. + * * @todo. */ function date_tools_wizard_create_content_type($name, $bundle, $description, $type_settings = array()) { @@ -332,8 +370,7 @@ function date_tools_wizard_create_content_type($name, $bundle, $description, $ty 'body_label' => 'Body', 'min_word_count' => '0', 'help' => '', - 'node_options' => - array( + 'node_options' => array( 'status' => 1, 'promote' => 1, 'sticky' => 0, @@ -374,8 +411,10 @@ function date_tools_wizard_create_content_type($name, $bundle, $description, $ty 'weight' => -4, 'module' => 'text', ), - 'settings' => array('display_summary' => TRUE), - 'display' => array( + 'settings' => array( + 'display_summary' => TRUE, + ), + 'display' => array( 'default' => array( 'label' => 'hidden', 'type' => 'text_default', diff --git a/sites/all/modules/contrib/fields/date/date_tools/tests/date_tools.test b/sites/all/modules/contrib/fields/date/date_tools/tests/date_tools.test index 47c0564..07af4a3 100644 --- a/sites/all/modules/contrib/fields/date/date_tools/tests/date_tools.test +++ b/sites/all/modules/contrib/fields/date/date_tools/tests/date_tools.test @@ -28,7 +28,7 @@ class DateToolsTestCase extends DrupalWebTestCase { // Create and log in our privileged user. $this->privileged_user = $this->drupalCreateUser( - array('administer content types', 'administer nodes', 'bypass node access', 'administer date tools') + array('administer content types', 'administer nodes', 'bypass node access', 'administer date tools', 'administer fields') ); $this->drupalLogin($this->privileged_user); diff --git a/sites/all/modules/contrib/fields/date/date_views/date_views.info b/sites/all/modules/contrib/fields/date/date_views/date_views.info index ef7da6c..68b32cc 100644 --- a/sites/all/modules/contrib/fields/date/date_views/date_views.info +++ b/sites/all/modules/contrib/fields/date/date_views/date_views.info @@ -9,13 +9,12 @@ files[] = includes/date_views_argument_handler.inc files[] = includes/date_views_argument_handler_simple.inc files[] = includes/date_views_filter_handler.inc files[] = includes/date_views_filter_handler_simple.inc -files[] = includes/date_views.views_default.inc files[] = includes/date_views.views.inc files[] = includes/date_views_plugin_pager.inc -; Information added by drupal.org packaging script on 2012-08-13 -version = "7.x-2.6" +; Information added by Drupal.org packaging script on 2017-04-07 +version = "7.x-2.10" core = "7.x" project = "date" -datestamp = "1344850024" +datestamp = "1491562090" diff --git a/sites/all/modules/contrib/fields/date/date_views/date_views.install b/sites/all/modules/contrib/fields/date/date_views/date_views.install new file mode 100644 index 0000000..e1d2e3e --- /dev/null +++ b/sites/all/modules/contrib/fields/date/date_views/date_views.install @@ -0,0 +1,54 @@ + 'Date views', + 'description' => 'Configure settings for date views.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('date_views_settings'), + 'access arguments' => array('administer site configuration'), + 'type' => MENU_LOCAL_TASK, + ); + + return $items; +} + +/** + * Form callback for date views settings. + */ +function date_views_settings($form, &$form_state) { + + $form['date_views_month_format_with_year'] = array( + '#type' => 'textfield', + '#title' => t('Date views month format with year'), + '#size' => 10, + '#default_value' => variable_get('date_views_month_format_with_year', 'F Y'), + '#description' => t('Date views month format with year, default value : F Y'), + ); + + $form['date_views_month_format_without_year'] = array( + '#type' => 'textfield', + '#title' => t('Date views month format without year'), + '#size' => 10, + '#default_value' => variable_get('date_views_month_format_without_year', 'F'), + '#description' => t('Date views month format without year, default value : F'), + ); + + $form['date_views_day_format_with_year'] = array( + '#type' => 'textfield', + '#title' => t('Date views day format with year'), + '#size' => 10, + '#default_value' => variable_get('date_views_day_format_with_year', 'l, F j, Y'), + '#description' => t('Date views day format with year, default value : l, F j, Y'), + ); + + $form['date_views_day_format_without_year'] = array( + '#type' => 'textfield', + '#title' => t('Date views day format without year'), + '#size' => 10, + '#default_value' => variable_get('date_views_day_format_without_year', 'l, F j'), + '#description' => t('Date views day format without year, default value : l, F j'), + ); + + $form['date_views_week_format_with_year'] = array( + '#type' => 'textfield', + '#title' => t('Date views week format with year'), + '#size' => 10, + '#default_value' => variable_get('date_views_week_format_with_year', 'F j, Y'), + '#description' => t('Date views week format with year, default value : F j, Y'), + ); + + $form['date_views_week_format_without_year'] = array( + '#type' => 'textfield', + '#title' => t('Date views week format without year'), + '#size' => 10, + '#default_value' => variable_get('date_views_week_format_without_year', 'F j'), + '#description' => t('Date views week format without year, default value : F j'), + ); + + return system_settings_form($form); + +} + /** * Implements hook_views_api(). * @@ -11,13 +90,30 @@ function date_views_theme() { 'file' => 'theme.inc', 'path' => "$path/theme", ); - return array( - 'date_nav_title' => $base + array('variables' => array('granularity' => NULL, 'view' => NULL, 'link' => NULL, 'format' => NULL)), - 'date_views_filter_form' => $base + array('template' => 'date-views-filter-form', 'render element' => 'form'), - 'date_calendar_day' => $base + array('variables' => array('date' => NULL)), + return array( + 'date_nav_title' => $base + array( + 'variables' => array( + 'granularity' => NULL, + 'view' => NULL, + 'link' => NULL, + 'format' => NULL, + ), + ), + 'date_views_filter_form' => $base + array( + 'template' => 'date-views-filter-form', + 'render element' => 'form', + ), + 'date_calendar_day' => $base + array( + 'variables' => array( + 'date' => NULL, + ), + ), 'date_views_pager' => $base + array( - 'variables' => array('plugin' => NULL, 'input' => NULL), + 'variables' => array( + 'plugin' => NULL, + 'input' => NULL, + ), // Register a pattern so that it can work like all views templates. 'pattern' => 'date_views_pager__', 'template' => 'date-views-pager', @@ -25,6 +121,9 @@ function date_views_theme() { ); } +/** + * Implements hook_views_api(). + */ function date_views_views_api() { return array( 'api' => 3, @@ -44,7 +143,7 @@ function date_views_views_fetch_fields($base, $type) { } /** - * Identify all potential date/timestamp fields and cache the data. + * Identify all potential date/timestamp fields and cache the data. */ function date_views_fields($base = 'node', $reset = FALSE) { static $fields = array(); @@ -66,8 +165,8 @@ function date_views_fields($base = 'node', $reset = FALSE) { /** * Implements hook_date_views_entities(). - * Map extra Views tables to the entity that holds its date fields, - * needed for Views tables other than the primary tables identified in entity_info(). + * + * Map extra Views tables to the entity that holds its date fields, needed for Views tables other than the primary tables identified in entity_info(). */ function date_views_date_views_extra_tables() { return array( @@ -76,14 +175,13 @@ function date_views_date_views_extra_tables() { } /** - * Helper function to map entity types to the Views base table they use, - * to make it easier to infer the entity type from a base table. + * Helper function to map entity types to the Views base table they use, to make it easier to infer the entity type from a base table. * - * Views has a new handler called views_handler_field_entity() that loads - * entities, and you can use something like the following to get the - * entity type from a view, but not all our base tables contain the - * entity information we need, (i.e. revisions) so it won't work here - * and we resort to creating information from entity_get_info(). + * Views has a new handler called views_handler_field_entity() that loads entities. + * + * And you can use something like the following to get the entity type from a view, but not all our base tables contain the entity information we need, (i.e. revisions). + * + * So it won't work here and we resort to creating information from entity_get_info(). * * // A method to get the entity type for a base table. * $table_data = views_fetch_data($base_table); @@ -118,11 +216,7 @@ function date_views_base_tables() { /** * Implements hook_date_views_fields(). * - * All modules that create custom fields that use the - * 'views_handler_field_date' handler can provide - * additional information here about the type of - * date they create so the date can be used by - * the Date API views date argument and date filter. + * All modules that create custom fields that use the 'views_handler_field_date' handler can provide additional information here about the type of date they create so the date can be used by the Date API views date argument and date filter. */ function date_views_date_views_fields($field) { $values = array( @@ -188,12 +282,15 @@ function date_pager_url($view, $date_type = NULL, $date_arg = NULL, $force_view_ case 'year': $args[$pos] = date_pad($view->date_info->year, 4); break; + case 'week': $args[$pos] = date_pad($view->date_info->year, 4) . '-W' . date_pad($view->date_info->week); break; + case 'day': $args[$pos] = date_pad($view->date_info->year, 4) . '-' . date_pad($view->date_info->month) . '-' . date_pad($view->date_info->day); break; + default: $args[$pos] = date_pad($view->date_info->year, 4) . '-' . date_pad($view->date_info->month); break; @@ -223,9 +320,14 @@ function date_pager_url($view, $date_type = NULL, $date_arg = NULL, $force_view_ // if they use exposed filters. return url($view->get_url($args), array( 'query' => date_views_querystring($view), - 'absolute' => $absolute)); + 'absolute' => $absolute, + ) + ); } +/** + * Identifier of a date block. + */ function date_block_identifier($view) { if (!empty($view->block_identifier)) { return $view->block_identifier; @@ -236,12 +338,9 @@ function date_block_identifier($view) { /** * Implements hook_field_views_data_alter(). * - * Create a Views field for each date column we care about - * to supplement the generic 'entity_id' and 'revision_id' - * fields that are automatically created. + * Create a Views field for each date column we care about to supplement the generic 'entity_id' and 'revision_id' fields that are automatically created. * - * Also use friendlier labels to distinguish the start date - * and end date in listings (for fields that use both). + * Also use friendlier labels to distinguish the start date and end date in listings (for fields that use both). */ function date_views_field_views_data_alter(&$result, $field, $module) { if ($module == 'date') { @@ -261,8 +360,8 @@ function date_views_field_views_data_alter(&$result, $field, $module) { $result[$table][$column]['field']['is date'] = TRUE; // Not sure yet if we still need a custom field handler in D7 now that custom formatters are available. // Might still need it to handle grouping of multiple value dates. - //$result[$table][$column]['field']['handler'] = 'date_handler_field_date'; - //$result[$table][$column]['field']['add fields to query'] = TRUE; + // $result[$table][$column]['field']['handler'] = 'date_handler_field_date'; + // $result[$table][$column]['field']['add fields to query'] = TRUE; } // For filters, arguments, and sorts, determine if this column is for @@ -320,12 +419,25 @@ function date_views_field_views_data_alter(&$result, $field, $module) { // translatable string. This is a hack to get it to appear right // before 'end date' in the listing (i.e., in a non-alphabetical, // but more user friendly, order). - $result[$table][$column]['title'] = t('@label - start date (!name)', array('@label' => $label, '!name' => $field['field_name'])); - $result[$table][$column]['title short'] = t('@label - start date', array('@label' => $label)); + $result[$table][$column]['title'] = t('@label - start date (!name)', array( + '@label' => $label, + '!name' => $field['field_name'], + )); + $result[$table][$column]['title short'] = t('@label - start date', array( + '@label' => $label, + )); break; + case 'value2': - $result[$table][$column]['title'] = t('@label - end date (!name:!column)', array('@label' => $label, '!name' => $field['field_name'], '!column' => $this_column)); - $result[$table][$column]['title short'] = t('@label - end date:!column', array('@label' => $label, '!column' => $this_column)); + $result[$table][$column]['title'] = t('@label - end date (!name:!column)', array( + '@label' => $label, + '!name' => $field['field_name'], + '!column' => $this_column, + )); + $result[$table][$column]['title short'] = t('@label - end date:!column', array( + '@label' => $label, + '!column' => $this_column, + )); break; } } @@ -346,18 +458,15 @@ function date_views_form_views_ui_edit_form_alter(&$form, &$form_state, $form_id } /** - * The instanceof function makes this work for any handler that was derived - * from 'views_handler_filter_date' or 'views_handler_argument_date', - * which includes core date fields like the node updated field. + * The instanceof function makes this work for any handler that was derived from 'views_handler_filter_date' or 'views_handler_argument_date', which includes core date fields like the node updated field. * - * The test for $handler->min_date tells us that this is an argument that - * not only is derived from the views date handler but also has been processed - * by the Date Views filter or argument code. -*/ + * The test for $handler->min_date tells us that this is an argument that not only is derived from the views date handler but also has been processed by the Date Views filter or argument code. + */ function date_views_handler_is_date($handler, $type = 'argument') { switch ($type) { case 'filter': return $handler instanceof views_handler_filter_date && !empty($handler->min_date); + case 'argument': return $handler instanceof views_handler_argument_date && !empty($handler->min_date); } @@ -366,8 +475,8 @@ function date_views_handler_is_date($handler, $type = 'argument') { /** * Validation hook for exposed filters that use the select widget. - * This is to ensure the the user completes all parts of the date - * not just some parts. Only needed for the select widget. + * + * This is to ensure the the user completes all parts of the date not just some parts. Only needed for the select widget. */ function date_views_select_validate(&$form, &$form_state) { // If there are no values just return. @@ -378,7 +487,7 @@ function date_views_select_validate(&$form, &$form_state) { $filled = array(); $value = drupal_array_get_nested_value($form_state['input'], $form['#parents']); foreach ($granularity as $part) { - if (!empty($value['value'][$part])) { + if (isset($value['value']) && is_numeric($value['value'][$part])) { $filled[] = $part; } } @@ -389,18 +498,23 @@ function date_views_select_validate(&$form, &$form_state) { case 'year': form_error($form['value'][$part], t('Please choose a year.'), $form_state); break; + case 'month': form_error($form['value'][$part], t('Please choose a month.'), $form_state); break; + case 'day': form_error($form['value'][$part], t('Please choose a day.'), $form_state); break; + case 'hour': form_error($form['value'][$part], t('Please choose an hour.'), $form_state); break; + case 'minute': form_error($form['value'][$part], t('Please choose a minute.'), $form_state); break; + case 'second': form_error($form['value'][$part], t('Please choose a second.'), $form_state); break; @@ -412,8 +526,7 @@ function date_views_select_validate(&$form, &$form_state) { /** * Implements hook_date_formatter_view_alter(). * - * If we are displaying a date from a view, see if we have information about - * which multiple value to display. If so, set the date_id in the entity. + * If we are displaying a date from a view, see if we have information about which multiple value to display. If so, set the date_id in the entity. */ function date_views_date_formatter_pre_view_alter(&$entity, &$variables) { // Some views have no row index. @@ -426,4 +539,4 @@ function date_views_date_formatter_pre_view_alter(&$entity, &$variables) { $entity->date_id = 'date.' . $date_item->$date_id . '.' . $field['field_name'] . '.' . $date_item->$date_delta . '.0'; } } -} \ No newline at end of file +} diff --git a/sites/all/modules/contrib/fields/date/date_views/includes/date_plugin_display_attachment.inc b/sites/all/modules/contrib/fields/date/date_views/includes/date_plugin_display_attachment.inc index 94371f7..779f4d1 100644 --- a/sites/all/modules/contrib/fields/date/date_views/includes/date_plugin_display_attachment.inc +++ b/sites/all/modules/contrib/fields/date/date_views/includes/date_plugin_display_attachment.inc @@ -1,6 +1,7 @@ 'date_views', // This just tells our themes are elsewhere. + // This just tells our themes are elsewhere. + 'module' => 'date_views', 'display' => array( // Display plugin for date navigation. 'date_nav' => array( @@ -83,7 +85,7 @@ function date_views_views_plugins() { } /** - * Implements hook_views_data() + * Implements hook_views_data(). */ function date_views_views_data() { $data = array(); @@ -95,12 +97,12 @@ function date_views_views_data() { $data[$base_table]['date_argument'] = array( 'group' => t('Date'), 'title' => t('Date (!base_table)', array('!base_table' => $base_table)), - 'help' => t('Filter any Views !base_table date field by a date argument, using any common ISO date/period format (i.e. YYYY, YYYY-MM, YYYY-MM-DD, YYYY-W99, YYYY-MM-DD--P3M, P90D, etc). ', array('!base_table' => $base_table)), + 'help' => t('Filter any Views !base_table date field by a date argument, using any common ISO date/period format (i.e. YYYY, YYYY-MM, YYYY-MM-DD, YYYY-W99, YYYY-MM-DD--P3M, P90D, etc).', array('!base_table' => $base_table)), 'argument' => array( 'handler' => 'date_views_argument_handler', 'empty field name' => t('Undated'), 'is date' => TRUE, - //'skip base' => $base_table, + // 'skip base' => $base_table, ), ); // The flexible date filter. @@ -112,7 +114,7 @@ function date_views_views_data() { 'handler' => 'date_views_filter_handler', 'empty field name' => t('Undated'), 'is date' => TRUE, - //'skip base' => $base_table, + // 'skip base' => $base_table, ), ); } @@ -128,7 +130,7 @@ function date_views_views_data_alter(&$data) { // Mark all the core date handlers as date fields. // This will identify all handlers that directly use the _date handlers, // will not pick up any that extend those handlers. - foreach ($data as $module => &$table) { + foreach ($data as $base_table => &$table) { foreach ($table as $id => &$field) { foreach (array('field', 'sort', 'filter', 'argument') as $type) { if (isset($field[$type]) && isset($field[$type]['handler']) && ($field[$type]['handler'] == 'views_handler_' . $type . '_date')) { @@ -140,8 +142,9 @@ function date_views_views_data_alter(&$data) { } /** - * Central function for setting up the right timezone values - * in the SQL date handler. + * Central function for setting up the right timezone values. + * + * In the SQL date handler. * * The date handler will use this information to decide if the * database value needs a timezone conversion. @@ -152,30 +155,45 @@ function date_views_views_data_alter(&$data) { */ function date_views_set_timezone(&$date_handler, &$view, $field) { switch ($field['tz_handling']) { - case 'date' : + case 'date': $date_handler->db_timezone = 'UTC'; $date_handler->local_timezone_field = $field['timezone_field']; $date_handler->offset_field = $field['offset_field']; break; + case 'none': $date_handler->db_timezone = date_default_timezone(); $date_handler->local_timezone = date_default_timezone(); break; + case 'utc': $date_handler->db_timezone = 'UTC'; $date_handler->local_timezone = 'UTC'; break; - default : + + default: $date_handler->db_timezone = 'UTC'; $date_handler->local_timezone = date_default_timezone(); break; } } +/** + * Helper function to generate a query string. + * + * @param object $view + * A View object. + * + * @param array $extra_params + * An extra parameters. + * + * @return null/string + * Return a query or NULL. + */ function date_views_querystring($view, $extra_params = array()) { $query_params = array_merge($_GET, $extra_params); // Allow NULL params to be removed from the query string. - foreach ($extra_params AS $key => $value) { + foreach ($extra_params as $key => $value) { if (!isset($value)) { unset($query_params[$key]); } diff --git a/sites/all/modules/contrib/fields/date/date_views/includes/date_views_argument_handler.inc b/sites/all/modules/contrib/fields/date/date_views/includes/date_views_argument_handler.inc index 61aeafc..20eedff 100644 --- a/sites/all/modules/contrib/fields/date/date_views/includes/date_views_argument_handler.inc +++ b/sites/all/modules/contrib/fields/date/date_views/includes/date_views_argument_handler.inc @@ -3,12 +3,14 @@ * @file * Date API views argument handler. * This argument combines multiple date arguments into a single argument - * where all fields are controlled by the same date and can be combined with either AND or OR. + * where all fields are controlled by the same date and can be combined + * with either AND or OR. */ /** * Date API argument handler. */ +// @codingStandardsIgnoreStart class date_views_argument_handler extends date_views_argument_handler_simple { /** @@ -198,3 +200,4 @@ class date_views_argument_handler extends date_views_argument_handler_simple { } } +// @codingStandardsIgnoreEnd diff --git a/sites/all/modules/contrib/fields/date/date_views/includes/date_views_argument_handler_simple.inc b/sites/all/modules/contrib/fields/date/date_views/includes/date_views_argument_handler_simple.inc index faff05a..2839b95 100644 --- a/sites/all/modules/contrib/fields/date/date_views/includes/date_views_argument_handler_simple.inc +++ b/sites/all/modules/contrib/fields/date/date_views/includes/date_views_argument_handler_simple.inc @@ -7,11 +7,13 @@ /** * Date API argument handler. */ +// @codingStandardsIgnoreStart class date_views_argument_handler_simple extends views_handler_argument_date { /** - * Get granularity and use it to create the formula and a format - * for the results. + * Get granularity. + * + * Use it to create the formula and a format for the results. */ function init(&$view, &$options) { parent::init($view, $options); @@ -29,12 +31,14 @@ class date_views_argument_handler_simple extends views_handler_argument_date { $this->date_handler->local_timezone = date_get_timezone($field['settings']['tz_handling']); } $this->date_handler->granularity = $this->options['granularity']; - // This value needs to be initialized so it exists even if the query doesn't run. + // This value needs to be initialized so + // it exists even if the query doesn't run. $this->date_handler->placeholders = array(); $this->format = $this->date_handler->views_formats($this->date_handler->granularity, 'display'); $this->sql_format = $this->date_handler->views_formats($this->date_handler->granularity, 'sql'); - // $this->arg_format is the format the parent date handler will use to create a default argument. + // $this->arg_format is the format the parent date + // handler will use to create a default argument. $this->arg_format = $this->format(); // Identify the base table for this field. @@ -43,6 +47,9 @@ class date_views_argument_handler_simple extends views_handler_argument_date { } + /** + * {@inheritdoc} + */ function format() { if (!empty($this->options['granularity'])) { return $this->date_handler->views_formats($this->options['granularity']); @@ -53,8 +60,9 @@ class date_views_argument_handler_simple extends views_handler_argument_date { } /** - * Set the empty argument value to the current date, - * formatted appropriately for this argument. + * Set the empty argument value to the current date. + * + * Formatted appropriately for this argument. */ function get_default_argument($raw = FALSE) { $is_default = FALSE; @@ -64,7 +72,7 @@ class date_views_argument_handler_simple extends views_handler_argument_date { if ($granularity == 'week') { $now = date_now(); $week = date_week(date_format($now, 'Y-m-d')); - $value = date_format($now, 'Y') . '-W' . $week; + $value = date_format($now, 'o') . '-W' . date_pad($week); } else { $value = date($this->arg_format, REQUEST_TIME); @@ -85,7 +93,8 @@ class date_views_argument_handler_simple extends views_handler_argument_date { $options = parent::option_definition(); $options['year_range'] = array('default' => '-3:+3'); $options['granularity'] = array('default' => 'month'); - $options['default_argument_type'] = array('default' => 'date'); + $options['granularity_reset'] = array('default' => FALSE); + $options['default_argument_type']['default'] = 'date'; $options['add_delta'] = array('default' => ''); $options['use_fromto'] = array('default' => ''); $options['title_format'] = array('default' => ''); @@ -116,7 +125,9 @@ class date_views_argument_handler_simple extends views_handler_argument_date { '#attributes' => array('class' => array('dependent-options')), '#states' => array( 'visible' => array( - ':input[name="options[default_action]"]' => array('value' => 'summary') + ':input[name="options[default_action]"]' => array( + 'value' => 'summary', + ), ), ), ); @@ -129,23 +140,37 @@ class date_views_argument_handler_simple extends views_handler_argument_date { '#attributes' => array('class' => array('dependent-options')), '#states' => array( 'visible' => array( - ':input[name="options[title_format]"]' => array('value' => 'custom') + ':input[name="options[title_format]"]' => array( + 'value' => 'custom', + ), ), ), ); + // Get default granularity options $options = $this->date_handler->date_parts(); - unset($options['second'], $options['minute']); - $options += array('week' => t('Week', array(), array('context' => 'datetime'))); + // Add the 'week' option. + $options += array( + 'week' => t('Week', array(), array( + 'context' => 'datetime', + )), + ); + $form['granularity'] = array( '#title' => t('Granularity'), '#type' => 'radios', '#options' => $options, '#default_value' => $this->options['granularity'], - '#multiple' => TRUE, '#description' => t("Select the type of date value to be used in defaults, summaries, and navigation. For example, a granularity of 'month' will set the default date to the current month, summarize by month in summary views, and link to the next and previous month when using date navigation."), ); + $form['granularity_reset'] = array( + '#title' => t('Use granularity from argument value'), + '#type' => 'checkbox', + '#default_value' => $this->options['granularity_reset'], + '#description' => t("If the granularity of argument value is different from selected, use it from argument value."), + ); + $form['year_range'] = array( '#title' => t('Date year range'), '#type' => 'textfield', @@ -172,16 +197,18 @@ class date_views_argument_handler_simple extends views_handler_argument_date { '#default_value' => $this->options['add_delta'], '#options' => array('' => t('No'), 'yes' => t('Yes')), '#description' => t('Add an identifier to the view to show which multiple value date fields meet the filter criteria. Note: This option may introduce duplicate values into the view. Required when using multiple value fields in a Calendar or any time you want the node view of multiple value dates to display only the values that match the view filters.'), - // Only let mere mortals tweak this setting for multi-value fields + // Only let mere mortals tweak this setting for multi-value fields. '#access' => $access, ); - } + /** + * {@inheritdoc} + */ function options_validate(&$form, &$form_state) { // It is very important to call the parent function here: parent::options_validate($form, $form_state); - if (!preg_match('/^(?:\-[0-9]{1,4}|[0-9]{4}):(?:[\+|\-][0-9]{1,4}|[0-9]{4})$/', $form_state['values']['options']['year_range'])) { + if (!preg_match('/^(?:\-[0-9]{1,4}|[0-9]{4}):(?:[\+\-][0-9]{1,4}|[0-9]{4})$/', $form_state['values']['options']['year_range'])) { form_error($form['year_range'], t('Date year range must be in the format -9:+9, 2005:2010, -9:2010, or 2005:+9')); } } @@ -209,14 +236,15 @@ class date_views_argument_handler_simple extends views_handler_argument_date { $format = !empty($this->options['title_format_custom']) && !empty($this->options['title_format_custom']) ? $this->options['title_format_custom'] : $this->date_handler->views_formats($this->options['granularity'], 'display'); $range = $this->date_handler->arg_range($this->argument); return date_format_date($range[0], 'custom', $format); - } + } /** - * Provide the argument to use to link from the summary to the next level; - * this will be called once per row of a summary, and used as part of + * Provide the argument to use to link from the summary to the next level. + * + * This will be called once per row of a summary, and used as part of * $view->get_url(). * - * @param $data + * @param object $data * The query results for the row. */ function summary_argument($data) { @@ -234,10 +262,11 @@ class date_views_argument_handler_simple extends views_handler_argument_date { */ function summary_query() { - // @TODO The summary values are computed by the database. Unless the database has - // built-in timezone handling it will use a fixed offset, which will not be - // right for all dates. The only way I can see to make this work right is to - // store the offset for each date in the database so it can be added to the base + // @TODO The summary values are computed by the database. + // Unless the database has built-in timezone handling it will use + // a fixed offset, which will not be right for all dates. + // The only way I can see to make this work right is to store the offset + // for each date in the database so it can be added to the base // date value before the database formats the result. Because this is a huge // architectural change, it won't go in until we start a new branch. $this->formula = $this->date_handler->sql_format($this->sql_format, $this->date_handler->sql_field("***table***.$this->real_field")); @@ -245,7 +274,8 @@ class date_views_argument_handler_simple extends views_handler_argument_date { // Now that our table is secure, get our formula. $formula = $this->get_formula(); - // Add the field, give it an alias that does NOT match the actual field name or grouping won't work right. + // Add the field, give it an alias that does NOT match the actual + // field name or grouping won't work right. $this->base_alias = $this->name_alias = $this->query->add_field(NULL, $formula, $this->field . '_summary'); $this->query->set_count_field(NULL, $formula, $this->field); @@ -254,20 +284,22 @@ class date_views_argument_handler_simple extends views_handler_argument_date { /** * Inject a test for valid date range before the regular query. + * * Override the parent query to be able to control the $group. */ function query($group_by = FALSE) { - // @TODO Not doing anything with $group_by yet, need to figure out what has to be done. + // @TODO Not doing anything with $group_by yet, + // need to figure out what has to be done. if ($this->date_forbid()) { return; } // See if we need to reset granularity based on an argument value. - // Make sure we don't try to reset to some bogus value if someone has typed in an unexpected argument. - $granularity = $this->date_handler->arg_granularity($this->argument); - if (!empty($granularity)) { + // Make sure we don't try to reset to some bogus value if someone has + // typed in an unexpected argument. + if ($this->options['granularity_reset'] && $granularity = $this->date_handler->arg_granularity($this->argument)) { $this->date_handler->granularity = $granularity; $this->format = $this->date_handler->views_formats($this->date_handler->granularity, 'display'); $this->sql_format = $this->date_handler->views_formats($this->date_handler->granularity, 'sql'); @@ -276,7 +308,8 @@ class date_views_argument_handler_simple extends views_handler_argument_date { $this->ensure_my_table(); $group = !empty($this->options['date_group']) ? $this->options['date_group'] : 0; - // If requested, add the delta field to the view so we can later find the value that matched our query. + // If requested, add the delta field to the view so + // we can later find the value that matched our query. if (!empty($this->options['add_delta']) && (substr($this->real_field, -6) == '_value' || substr($this->real_field, -7) == '_value2')) { $this->query->add_field($this->table_alias, 'delta'); $real_field_name = str_replace(array('_value', '_value2'), '', $this->real_field); @@ -291,7 +324,8 @@ class date_views_argument_handler_simple extends views_handler_argument_date { $view_max_placeholder = $this->placeholder(); $this->date_handler->placeholders = array($view_min_placeholder => $view_min, $view_max_placeholder => $view_max); - // Are we comparing this field only or the Start/End date range to the view criteria? + // Are we comparing this field only or the Start/End date range + // to the view criteria? if (!empty($this->options['use_fromto'])) { // The simple case, match the field to the view range. @@ -302,10 +336,14 @@ class date_views_argument_handler_simple extends views_handler_argument_date { } else { - // Look for the intersection of the range of the date field with the range of the view. - // Get the Start/End values for this field. Retrieve using the original table name. - // Swap the current table name (adjusted for relationships) into the query. - // @TODO We may be able to use Views substitutions here, investigate that later. + // Look for the intersection of the range + // of the date field with the range of the view. + // Get the Start/End values for this field. + // Retrieve using the original table name. + // Swap the current table name (adjusted for relationships) + // into the query. + // @TODO We may be able to use Views substitutions here, + // investigate that later. $fields = date_views_fields($this->base_table); $fields = $fields['name']; $fromto = $fields[$this->original_table . '.' . $this->real_field]['fromto']; @@ -321,7 +359,10 @@ class date_views_argument_handler_simple extends views_handler_argument_date { } /** - * Add a callback to determine if we have moved outside the valid date range for this argument. + * Add a callback. + * + * To determine if we have moved outside + * the valid date range for this argument. */ function date_forbid() { if (empty($this->argument)) { @@ -343,3 +384,4 @@ class date_views_argument_handler_simple extends views_handler_argument_date { } } +// @codingStandardsIgnoreEnd diff --git a/sites/all/modules/contrib/fields/date/date_views/includes/date_views_fields.inc b/sites/all/modules/contrib/fields/date/date_views/includes/date_views_fields.inc index a002b08..d497ef6 100644 --- a/sites/all/modules/contrib/fields/date/date_views/includes/date_views_fields.inc +++ b/sites/all/modules/contrib/fields/date/date_views/includes/date_views_fields.inc @@ -5,13 +5,11 @@ */ /** - * Identify all potential date/timestamp fields. + * Identify all potential date/timestamp fields. * - * @return - * array with fieldname, type, and table. - * @see - * date_views_date_views_fields() which implements - * the hook_date_views_fields() for the core date fields. + * @return array + * An array with fieldname, type, and table. + * @see date_views_date_views_fields() */ function _date_views_fields($base = 'node') { @@ -60,7 +58,7 @@ function _date_views_fields($base = 'node') { $handler = views_get_handler($table_name, $field_name, 'filter'); $handler_name = $handler->definition['handler']; - // We don't care about anything but date handlers + // We don't care about anything but date handlers. if (empty($handler->definition['is date'])) { continue; } @@ -72,14 +70,17 @@ function _date_views_fields($base = 'node') { $field = field_info_field($handler->definition['field_name']); $is_field = TRUE; switch ($field['type']) { - case 'date': + case 'date': $sql_type = DATE_ISO; break; + case 'datestamp': break; + case 'datetime': $sql_type = DATE_DATETIME; break; + default: // If this is not a date field, nothing more to do. continue; @@ -88,7 +89,8 @@ function _date_views_fields($base = 'node') { $revision = in_array($base, array('node_revision')) ? FIELD_LOAD_REVISION : FIELD_LOAD_CURRENT; $db_info = date_api_database_info($field, $revision); $name = $table_name . "." . $field_name; - $granularity = !empty($field['granularity']) ? $field['granularity'] : array('year', 'month', 'day', 'hour', 'minute', 'second'); + $grans = array('year', 'month', 'day', 'hour', 'minute', 'second'); + $granularity = !empty($field['granularity']) ? $field['granularity'] : $grans; $fromto = array( $table_name . '.' . $db_info['columns'][$table_name]['value'], diff --git a/sites/all/modules/contrib/fields/date/date_views/includes/date_views_filter_handler.inc b/sites/all/modules/contrib/fields/date/date_views/includes/date_views_filter_handler.inc index 5eb5ebc..ae6944e 100644 --- a/sites/all/modules/contrib/fields/date/date_views/includes/date_views_filter_handler.inc +++ b/sites/all/modules/contrib/fields/date/date_views/includes/date_views_filter_handler.inc @@ -3,9 +3,11 @@ * @file * A flexible, configurable date filter. * This filter combines multiple date filters into a single filter - * where all fields are controlled by the same date and can be combined with either AND or OR. + * where all fields are controlled by the same date and can be combined + * with either AND or OR. */ +// @codingStandardsIgnoreStart class date_views_filter_handler extends date_views_filter_handler_simple { function init(&$view, &$options) { parent::init($view, $options); @@ -36,6 +38,36 @@ class date_views_filter_handler extends date_views_filter_handler_simple { $this->date_combine_conditions('op_simple'); } + function op_contains($field) { + $this->date_combine_conditions('op_contains'); + } + + function op_empty($field) { + $this->get_query_fields(); + if (empty($this->query_fields)) { + return; + } + + // Add each condition to the custom filter group. + foreach ((array) $this->query_fields as $query_field) { + $field = $query_field['field']; + $this->date_handler = $query_field['date_handler']; + + // Respect relationships when determining the table alias. + if ($field['table_name'] != $this->table || !empty($this->relationship)) { + $this->related_table_alias = $this->query->ensure_table($field['table_name'], $this->relationship); + } + else { + $this->related_table_alias = NULL; + } + + $table_alias = !empty($this->related_table_alias) ? $this->related_table_alias : $field['table_name']; + $field_name = $table_alias . '.' . $field['field_name']; + + parent::op_empty($field_name); + } + } + /** * Combines multiple date WHERE expressions into a single WHERE expression. * @@ -60,6 +92,9 @@ class date_views_filter_handler extends date_views_filter_handler_simple { if ($field['table_name'] != $this->table || !empty($this->relationship)) { $this->related_table_alias = $this->query->ensure_table($field['table_name'], $this->relationship); } + else { + $this->related_table_alias = NULL; + } $table_alias = !empty($this->related_table_alias) ? $this->related_table_alias : $field['table_name']; $field_name = $table_alias . '.' . $field['field_name']; @@ -175,3 +210,4 @@ class date_views_filter_handler extends date_views_filter_handler_simple { } } } +// @codingStandardsIgnoreEnd diff --git a/sites/all/modules/contrib/fields/date/date_views/includes/date_views_filter_handler_simple.inc b/sites/all/modules/contrib/fields/date/date_views/includes/date_views_filter_handler_simple.inc index 8a46253..4fa4c40 100644 --- a/sites/all/modules/contrib/fields/date/date_views/includes/date_views_filter_handler_simple.inc +++ b/sites/all/modules/contrib/fields/date/date_views/includes/date_views_filter_handler_simple.inc @@ -1,9 +1,11 @@ t('Contains'), + 'method' => 'op_contains', + 'short' => t('contains'), + 'values' => 1, + ); + return $operators; + } + /** * Helper function to find a default value. */ @@ -53,8 +66,8 @@ class date_views_filter_handler_simple extends views_handler_filter_date { // If this is a remembered value, use the value from the SESSION. if (!empty($this->options['expose']['remember'])) { $display_id = ($this->view->display_handler->is_defaulted('filters')) ? 'default' : $this->view->current_display; - if (!empty($_SESSION['views'][$this->view->name][$display_id]['date_filter'][$prefix])) { - return $_SESSION['views'][$this->view->name][$display_id]['date_filter'][$prefix]; + if (!empty($_SESSION['views'][$this->view->name][$display_id][$this->options['expose']['identifier']][$prefix])) { + return $_SESSION['views'][$this->view->name][$display_id][$this->options['expose']['identifier']][$prefix]; } } @@ -104,8 +117,12 @@ class date_views_filter_handler_simple extends views_handler_filter_date { $element_input['value'] = $this->get_filter_value('value', !empty($element_input['value']) ? $element_input['value'] : ''); $element_input['min'] = $this->get_filter_value('min', !empty($element_input['min']) ? $element_input['min'] : ''); $element_input['max'] = $this->get_filter_value('max', !empty($element_input['max']) ? $element_input['max'] : ''); - unset($element_input['default_date']); - unset($element_input['default_to_date']); + if (is_array($element_input) && isset($element_input['default_date'])) { + unset($element_input['default_date']); + } + if (is_array($element_input) && isset($element_input['default_to_date'])) { + unset($element_input['default_to_date']); + } $input[$this->options['expose']['identifier']] = $element_input; } @@ -163,6 +180,29 @@ class date_views_filter_handler_simple extends views_handler_filter_date { $this->query->add_where_expression($group, "$field $this->operator $placeholder", array($placeholder => $value)); } + function op_contains($field) { + + // Add the delta field to the view so we can later find the value that matched our query. + list($table_name, $field_name) = explode('.', $field); + if (!empty($this->options['add_delta']) && (substr($field_name, -6) == '_value' || substr($field_name, -7) == '_value2')) { + $this->query->add_field($table_name, 'delta'); + } + + $value = $this->get_filter_value('value', $this->value['value']); + $comp_date = new DateObject($value, date_default_timezone(), $this->format); + $fields = date_views_fields($this->base_table); + $fields = $fields['name']; + $fromto = $fields[$field]['fromto']; + $field_min = $this->date_handler->sql_field($fromto[0], NULL, $comp_date); + $field_min = $this->date_handler->sql_format($this->format, $field_min); + $field_max = $this->date_handler->sql_field($fromto[1], NULL, $comp_date); + $field_max = $this->date_handler->sql_format($this->format, $field_max); + $placeholder_min = $this->placeholder(); + $placeholder_max = $this->placeholder(); + $group = !empty($this->options['date_group']) ? $this->options['date_group'] : $this->options['group']; + $this->query->add_where_expression($group, "$field_max >= $placeholder_min AND $field_min <= $placeholder_max", array($placeholder_min => $value, $placeholder_max => $value)); + } + /** * Set the granularity of the date parts to use in the filter. */ @@ -225,7 +265,7 @@ class date_views_filter_handler_simple extends views_handler_filter_date { } function extra_options_validate($form, &$form_state) { - if (!preg_match('/^(?:\-[0-9]{1,4}|[0-9]{4}):(?:[\+|\-][0-9]{1,4}|[0-9]{4})$/', $form_state['values']['options']['year_range'])) { + if (!preg_match('/^(?:[\+\-][0-9]{1,4}|[0-9]{4}):(?:[\+\-][0-9]{1,4}|[0-9]{4})$/', $form_state['values']['options']['year_range'])) { form_error($form['year_range'], t('Date year range must be in the format -9:+9, 2005:2010, -9:2010, or 2005:+9')); } } @@ -294,7 +334,7 @@ class date_views_filter_handler_simple extends views_handler_filter_date { * @return * The form date part element for this instance. */ - function date_parts_form($form_state, $prefix, $source, $which, $operator_values, $identifier, $relative_id) { + function date_parts_form(&$form_state, $prefix, $source, $which, $operator_values, $identifier, $relative_id) { module_load_include('inc', 'date_api', 'date_api_elements'); switch ($prefix) { case 'min': @@ -316,7 +356,7 @@ class date_views_filter_handler_simple extends views_handler_filter_date { $type = 'date_text'; } - $format = $this->date_handler->views_formats($this->options['granularity'], 'sql'); + $format = $this->date_handler->views_formats($this->options['granularity'], 'display'); $granularity = array_keys($this->date_handler->date_parts($this->options['granularity'])); $relative_value = ($prefix == 'max' ? $this->options['default_to_date'] : $this->options['default_date']); @@ -333,7 +373,7 @@ class date_views_filter_handler_simple extends views_handler_filter_date { '#date_label_position' => 'within', '#date_year_range' => $this->options['year_range'], '#process' => array($type . '_element_process'), - '#prefix' => '
    ', + '#prefix' => '
    ', '#suffix' => '
    ', ); if ($which == 'all') { @@ -341,7 +381,10 @@ class date_views_filter_handler_simple extends views_handler_filter_date { $form[$prefix]['#dependency'] = array($source => $operator_values); } if (!isset($form_state['input'][$identifier][$prefix])) { - $form_state['input'][$identifier][$prefix] = $this->value[$prefix]; + // Ensure these exist. + foreach ($granularity as $key) { + $form_state['input'][$identifier][$prefix][$key] = NULL; + } } } else { @@ -368,7 +411,7 @@ class date_views_filter_handler_simple extends views_handler_filter_date { '#date_label_position' => 'within', '#date_year_range' => $this->options['year_range'], '#process' => array($type . '_element_process'), - '#prefix' => '
    ', + '#prefix' => '
    ', '#suffix' => '
    ', '#states' => array( 'visible' => array( @@ -492,3 +535,4 @@ class date_views_filter_handler_simple extends views_handler_filter_date { } } +// @codingStandardsIgnoreEnd diff --git a/sites/all/modules/contrib/fields/date/date_views/includes/date_views_plugin_pager.inc b/sites/all/modules/contrib/fields/date/date_views/includes/date_views_plugin_pager.inc index f9594a7..1addd20 100644 --- a/sites/all/modules/contrib/fields/date/date_views/includes/date_views_plugin_pager.inc +++ b/sites/all/modules/contrib/fields/date/date_views/includes/date_views_plugin_pager.inc @@ -2,50 +2,74 @@ /** * @file * Date pager. - * Works with a Date argument, the argument filters the view and the pager provides back/next navigation. + * Works with a Date argument, the argument filters + * the view and the pager provides back/next navigation. * * USER NOTES: * * To use this, add a pager to a view, and choose the option to 'Page by date'. * There are several settings: - * - The pager id: Set an id to be used as the identifier in the url for pager values, defaults to 'date'. - * - Pager position: Choose whether to display the date pager above, below, or both above and below the content. - * - Link format: Choose whether the pager links will be in the simple 'calendar/2011-12' format or the - * more complex 'calendar/?date=2011-12' pager format. The second one is more likely to work correctly - * if the pager is used in blocks and panels. + * - The pager id: Set an id to be used as the identifier + * in the url for pager values, defaults to 'date'. + * - Pager position: Choose whether to display the date + * pager above, below, or both above and below the content. + * - Link format: Choose whether the pager links will be in t + * he simple 'calendar/2011-12' format or the + * more complex 'calendar/?date=2011-12' pager format. + * The second one is more likely to work correctly + * if the pager is used in blocks and panels. * - * The pager works in combination with a Date argument and it will use the date fields and granularity - * set in that argument to create its back/next links. If the view has no Date argument, the pager can - * do nothing. The argument can either be a 'Date' argument that lets you select one or more date fields - * in the argument, or the simple 'Content' argument for an individual date field. It must be an + * The pager works in combination with a Date argument + * and it will use the date fields and granularity + * set in that argument to create its back/next links. + * If the view has no Date argument, the pager can + * do nothing. The argument can either be a 'Date' argument + * that lets you select one or more date fields + * in the argument, or the simple 'Content' argument for an + * individual date field. It must be an * argument that uses the date argument handler. * * DEVELOPER NOTES * - * The pager could technically create a query of its own rather than depending on the date argument to - * set the query, but it has only a limited set of tools to work with because it is a plugin, not a handler: - * it has no knowledge about relationships, it cannot use the ensure_my_table() function, - * plugins are not even invoked in pre_query(), so can't do anything there. + * The pager could technically create a query of its own rather + * than depending on the date argument to + * set the query, but it has only a limited set of tools to work + * with because it is a plugin, not a handler: + * it has no knowledge about relationships, it cannot use the + * ensure_my_table() function, plugins are not even invoked in pre_query(), + * so can't do anything there. * - * My conclusion was that the date pager simply is not powerful enough to create its own queries for - * date fields, which require very complex queries. Instead, we can combine this with a date argument and - * let the argument create the query and let the pager just provide the back/next links. If there is no + * My conclusion was that the date pager simply + * is not powerful enough to create its own queries for + * date fields, which require very complex queries. + * Instead, we can combine this with a date argument and + * let the argument create the query and let the pager + * just provide the back/next links. If there is no * date argument, the pager will do nothing. * - * There are still other problems. The pager is not even initialized until after all the handlers - * have created their queries, so it has no chance to alter values ahead of that. And the argument - * has no knowledge of the pager, so it can't check for pager values before the query is created. + * There are still other problems. The pager is not even + * initialized until after all the handlers + * have created their queries, so it has no chance + * to alter values ahead of that. And the argument + * has no knowledge of the pager, so it can't check + * for pager values before the query is created. * - * The solution used here is to let the argument create the original query. The pager query - * runs after that, so the pager checks to see if there is a pager value that needs to be used in the query. - * The date argument has identified the placeholders it used in the query. So if a change is needed, - * we can swap the pager value into the query created by the date argument and adjust the - * $view->date_info values set by the argument accordingly so the theme will pick up the new information. + * The solution used here is to let the argument create + * the original query. The pager query + * runs after that, so the pager checks to see + * if there is a pager value that needs to be used in the query. + * The date argument has identified the placeholders + * it used in the query. So if a change is needed, + * we can swap the pager value into the query created + * by the date argument and adjust the + * $view->date_info values set by the argument accordingly + * so the theme will pick up the new information. */ /** * Example plugin to handle paging by month. */ +// @codingStandardsIgnoreStart class date_views_plugin_pager extends views_plugin_pager { /** @@ -79,6 +103,7 @@ class date_views_plugin_pager extends views_plugin_pager { $options['link_format'] = array('default' => 'pager'); $options['date_argument'] = array('default' => 'Unknown'); $options['granularity'] = array('default' => 'Unknown'); + $options['skip_empty_pages'] = array('default' => FALSE); return $options; } @@ -110,6 +135,12 @@ class date_views_plugin_pager extends views_plugin_pager { '#default_value' => $this->options['link_format'], '#required' => TRUE, ); + $form['skip_empty_pages'] = array( + '#title' => t('Skip empty pages'), + '#type' => 'checkbox', + '#description' => t('When selected, the pager will not display pages with no result for the given date. This causes a slight performance degradation because two additional queries need to be executed.'), + '#default_value' => $this->options['skip_empty_pages'], + ); $form['date_argument']['#type'] = 'hidden'; $form['date_argument']['#value'] = $this->options['date_argument']; $form['granularity']['#type'] = 'hidden'; @@ -150,13 +181,7 @@ class date_views_plugin_pager extends views_plugin_pager { // Reset values set by argument if pager requires it. if (!empty($value)) { - $argument->argument = $value; - $argument->date_range = $argument->date_handler->arg_range($value); - $argument->min_date = $argument->date_range[0]; - $argument->max_date = $argument->date_range[1]; - // $argument->is_default works correctly for normal arguments, but does not - // work correctly if we are swapping in a new value from the pager. - $argument->is_default = FALSE; + $this->set_argument_value($argument, $value); } // The pager value might move us into a forbidden range, so test it. @@ -164,13 +189,102 @@ class date_views_plugin_pager extends views_plugin_pager { $this->view->build_info['fail'] = TRUE; return; } - - if (empty($this->view->date_info)) $this->view->date_info = new stdClass(); + // Write date_info to store information to be used + // in the theming functions. + if (empty($this->view->date_info)) { + $this->view->date_info = new stdClass(); + } $this->view->date_info->granularity = $argument->date_handler->granularity; $format = $this->view->date_info->granularity == 'week' ? DATE_FORMAT_DATETIME : $argument->sql_format; $this->view->date_info->placeholders = isset($argument->placeholders) ? $argument->placeholders : $argument->date_handler->placeholders; $this->view->date_info->date_arg = $argument->argument; $this->view->date_info->date_arg_pos = $i; + $this->view->date_info->limit = $argument->limit; + $this->view->date_info->url = $this->view->get_url(); + $this->view->date_info->pager_id = $this->options['date_id']; + $this->view->date_info->date_pager_position = $this->options['pager_position']; + $this->view->date_info->date_pager_format = $this->options['link_format']; + $this->view->date_info->skip_empty_pages = $this->options['skip_empty_pages'] == 1; + // Execute two additional queries to find + // the previous and next page with values. + if ($this->view->date_info->skip_empty_pages) { + $q = clone $argument->query; + $field = $argument->table_alias . '.' . $argument->real_field; + $fieldsql = $date_handler->sql_field($field); + $fieldsql = $date_handler->sql_format($format, $fieldsql); + $q->clear_fields(); + $q->orderby = array(); + $q->set_distinct(TRUE, TRUE); + // Date limits of this argument. + $datelimits = $argument->date_handler->arg_range($argument->limit[0] . '--' . $argument->limit[1]); + // Find the first two dates between the minimum date + // and the upper bound of the current value. + $q->add_orderby(NULL, $fieldsql, 'DESC', 'date'); + $this->set_argument_placeholders($this->view->date_info->placeholders, $datelimits[0], $argument->max_date, $q, $format); + + $compiledquery = $q->query(); + $compiledquery->range(0, 2); + $results = $compiledquery->execute()->fetchCol(0); + + $prevdate = array_shift($results); + $prevdatealt = array_shift($results); + // Find the first two dates between the lower bound + // of the current value and the maximum date. + $q->add_orderby(NULL, $fieldsql, 'ASC', 'date'); + $this->set_argument_placeholders($this->view->date_info->placeholders, $argument->min_date, $datelimits[1], $q, $format); + + $compiledquery = $q->query(); + $compiledquery->range(0, 2); + $results = $compiledquery->execute()->fetchCol(0); + + $nextdate = array_shift($results); + $nextdatealt = array_shift($results); + + // Set the default value of the query to $prevfirst or $nextfirst + // when there is no value and $prevsecond or $nextsecond is set. + if (empty($value)) { + // @Todo find out which of $prevdate or $nextdate is closest to the + // default argument date value and choose that one. + if ($prevdate && $prevdatealt) { + $this->set_argument_value($argument, $prevdate); + $value = $prevdate; + $prevdate = $prevdatealt; + // If the first next date is the same as the first previous date, + // move to the following next date. + if ($value == $nextdate) { + $nextdate = $nextdatealt; + $nextdatealt = NULL; + } + } + elseif ($nextdate && $nextdatealt) { + $this->set_argument_value($argument, $nextdate); + $value = $nextdate; + $nextdate = $nextdatealt; + // If the first previous date is the same as the first next date, + // move to the following previous date. + if ($value == $prevdate) { + $prevdate = $prevdatealt; + $prevdatealt = NULL; + } + } + } + else { + // $prevdate and $nextdate are the same as $value, so move to + // the next values. + $prevdate = $prevdatealt; + $nextdate = $nextdatealt; + } + + $this->view->date_info->prev_date = $prevdate ? new DateObject($prevdate, NULL, $format) : NULL; + $this->view->date_info->next_date = $nextdate ? new DateObject($nextdate, NULL, $format) : NULL; + } + else { + $this->view->date_info->prev_date = clone($argument->min_date); + date_modify($this->view->date_info->prev_date, '-1 ' . $argument->date_handler->granularity); + $this->view->date_info->next_date = clone($argument->min_date); + date_modify($this->view->date_info->next_date, '+1 ' . $argument->date_handler->granularity); + } + // Write the date_info properties that depend on the current value. $this->view->date_info->year = date_format($argument->min_date, 'Y'); $this->view->date_info->month = date_format($argument->min_date, 'n');; $this->view->date_info->day = date_format($argument->min_date, 'j'); @@ -178,11 +292,6 @@ class date_views_plugin_pager extends views_plugin_pager { $this->view->date_info->date_range = $argument->date_range; $this->view->date_info->min_date = $argument->min_date; $this->view->date_info->max_date = $argument->max_date; - $this->view->date_info->limit = $argument->limit; - $this->view->date_info->url = $this->view->get_url(); - $this->view->date_info->pager_id = $this->options['date_id']; - $this->view->date_info->date_pager_position = $this->options['pager_position']; - $this->view->date_info->date_pager_format = $this->options['link_format']; } $i++; } @@ -191,20 +300,33 @@ class date_views_plugin_pager extends views_plugin_pager { // If there is pager input and the argument has set the placeholders, // swap the pager value in for the placeholder set by the argument. if (!empty($value) && !empty($this->view->date_info->placeholders)) { - $placeholders = $this->view->date_info->placeholders; - $count = count($placeholders); - foreach ($this->view->query->where as $group => $data) { - foreach ($data['conditions'] as $delta => $condition) { - if (array_key_exists('value', $condition) && is_array($condition['value'])) { - foreach ($condition['value'] as $placeholder => $placeholder_value) { - if (array_key_exists($placeholder, $placeholders)) { - // If we didn't get a match, this is a > $min < $max query that uses the view - // min and max dates as placeholders. - $date = ($count == 2) ? $this->view->date_info->min_date : $this->view->date_info->max_date; - $next_placeholder = array_shift($placeholders); - $this->view->query->where[$group]['conditions'][$delta]['value'][$placeholder] = $date->format($format); - $count--; - } + $this->set_argument_placeholders($this->view->date_info->placeholders, $this->view->date_info->min_date, $this->view->date_info->max_date, $this->view->query, $format); + } + } + + function set_argument_value($argument, $value) { + $argument->argument = $value; + $argument->date_range = $argument->date_handler->arg_range($value); + $argument->min_date = $argument->date_range[0]; + $argument->max_date = $argument->date_range[1]; + // $argument->is_default works correctly for normal arguments, but does not + // work correctly if we are swapping in a new value from the pager. + $argument->is_default = FALSE; + } + + function set_argument_placeholders($placeholders, $mindate, $maxdate, $query, $format) { + $count = count($placeholders); + foreach ($query->where as $group => $data) { + foreach ($data['conditions'] as $delta => $condition) { + if (array_key_exists('value', $condition) && is_array($condition['value'])) { + foreach ($condition['value'] as $placeholder => $placeholder_value) { + if (array_key_exists($placeholder, $placeholders)) { + // If we didn't get a match, this is a > $min < $max query that uses the view + // min and max dates as placeholders. + $date = ($count == 2) ? $mindate : $maxdate; + $next_placeholder = array_shift($placeholders); + $query->where[$group]['conditions'][$delta]['value'][$placeholder] = $date->format($format); + $count--; } } } @@ -230,4 +352,5 @@ class date_views_plugin_pager extends views_plugin_pager { $pager_theme = views_theme_functions('date_views_pager', $this->view, $this->display); return theme($pager_theme, array('plugin' => $this, 'input' => $input)); } -} \ No newline at end of file +} +// @codingStandardsIgnoreEnd diff --git a/sites/all/modules/contrib/fields/date/date_views/theme/date-views-pager.tpl.php b/sites/all/modules/contrib/fields/date/date_views/theme/date-views-pager.tpl.php index 531214c..ac8cfb4 100644 --- a/sites/all/modules/contrib/fields/date/date_views/theme/date-views-pager.tpl.php +++ b/sites/all/modules/contrib/fields/date/date_views/theme/date-views-pager.tpl.php @@ -27,8 +27,10 @@ * be used in the l() function, including rel=nofollow. */ ?> - -
    + + + +

    @@ -36,14 +38,18 @@
    • - 'date_nav'))), $prev_url, $prev_options); ?> -  
    • + 'date_nav')); + print l(t($text), $prev_url, $prev_options); + ?> + -
    •   +
    • 'date_nav')) . ' ') . '»', $next_url, $next_options); ?>
    -
    \ No newline at end of file +
    diff --git a/sites/all/modules/contrib/fields/date/date_views/theme/theme.inc b/sites/all/modules/contrib/fields/date/date_views/theme/theme.inc index 4a7d4bc..7ca03f3 100644 --- a/sites/all/modules/contrib/fields/date/date_views/theme/theme.inc +++ b/sites/all/modules/contrib/fields/date/date_views/theme/theme.inc @@ -4,6 +4,7 @@ * @file * Theme files for Date Pager. */ + /** * Jump in and move the pager. */ @@ -15,9 +16,11 @@ function date_views_preprocess_views_view(&$vars) { $vars['header'] .= $vars['pager']; $vars['pager'] = ''; break; + case 'both': $vars['header'] .= $vars['pager']; break; + default: // Already on the bottom. } @@ -66,28 +69,37 @@ function template_preprocess_date_views_pager(&$vars) { } if (empty($date_info->hide_nav)) { - $prev_date = clone($min_date); - date_modify($prev_date, '-1 ' . $granularity); - $next_date = clone($min_date); - date_modify($next_date, '+1 ' . $granularity); - $format = array('year' => 'Y', 'month' => 'Y-m', 'day' => 'Y-m-d'); - switch ($granularity) { - case 'week': - $next_week = date_week(date_format($next_date, 'Y-m-d')); - $prev_week = date_week(date_format($prev_date, 'Y-m-d')); - $next_arg = date_format($next_date, 'Y-\W') . date_pad($next_week); - $prev_arg = date_format($prev_date, 'Y-\W') . date_pad($prev_week); - break; - default: - $next_arg = date_format($next_date, $format[$granularity]); - $prev_arg = date_format($prev_date, $format[$granularity]); + $prev_date = $date_info->prev_date; + $next_date = $date_info->next_date; + + $format = array('year' => 'Y', 'month' => 'Y-m', 'day' => 'Y-m-d', 'hour' => 'Y-m-d\TH'); + if (!empty($prev_date)) { + switch ($granularity) { + case 'week': + $prev_week = date_week(date_format($prev_date, 'Y-m-d')); + $prev_arg = date_format($prev_date, 'o-\W') . date_pad($prev_week); + break; + default: + $prev_arg = date_format($prev_date, $format[$granularity]); + } + $prev_path = str_replace($date_info->date_arg, $prev_arg, $date_info->url); + $prev_args[$pos] = $prev_arg; + $vars['prev_url'] = date_pager_url($view, NULL, $prev_arg); } - $next_path = str_replace($date_info->date_arg, $next_arg, $date_info->url); - $prev_path = str_replace($date_info->date_arg, $prev_arg, $date_info->url); - $next_args[$pos] = $next_arg; - $prev_args[$pos] = $prev_arg; - $vars['next_url'] = date_pager_url($view, NULL, $next_arg); - $vars['prev_url'] = date_pager_url($view, NULL, $prev_arg); + if (!empty($next_date)) { + switch ($granularity) { + case 'week': + $next_week = date_week(date_format($next_date, 'Y-m-d')); + $next_arg = date_format($next_date, 'o-\W') . date_pad($next_week); + break; + default: + $next_arg = date_format($next_date, $format[$granularity]); + } + $next_path = str_replace($date_info->date_arg, $next_arg, $date_info->url); + $next_args[$pos] = $next_arg; + $vars['next_url'] = date_pager_url($view, NULL, $next_arg); + } + $vars['next_options'] = $vars['prev_options'] = array(); } else { @@ -117,14 +129,17 @@ function template_preprocess_date_views_pager(&$vars) { $prev_title = t('Navigate to previous year'); $next_title = t('Navigate to next year'); break; + case 'month': $prev_title = t('Navigate to previous month'); $next_title = t('Navigate to next month'); break; + case 'week': $prev_title = t('Navigate to previous week'); $next_title = t('Navigate to next week'); break; + case 'day': $prev_title = t('Navigate to previous day'); $next_title = t('Navigate to next day'); @@ -157,32 +172,44 @@ function template_preprocess_date_views_pager(&$vars) { } /** - * Theme the calendar title + * Theme the calendar title. */ function theme_date_nav_title($params) { + $title = ''; $granularity = $params['granularity']; $view = $params['view']; $date_info = $view->date_info; $link = !empty($params['link']) ? $params['link'] : FALSE; $format = !empty($params['format']) ? $params['format'] : NULL; + $format_with_year = variable_get('date_views_' . $granularity . '_format_with_year', 'l, F j, Y'); + $format_without_year = variable_get('date_views_' . $granularity . '_format_without_year', 'l, F j'); switch ($granularity) { case 'year': $title = $date_info->year; $date_arg = $date_info->year; break; + case 'month': - $format = !empty($format) ? $format : (empty($date_info->mini) ? 'F Y' : 'F'); + $format = !empty($format) ? $format : (empty($date_info->mini) ? $format_with_year : $format_without_year); $title = date_format_date($date_info->min_date, 'custom', $format); $date_arg = $date_info->year . '-' . date_pad($date_info->month); break; + case 'day': - $format = !empty($format) ? $format : (empty($date_info->mini) ? 'l, F j, Y' : 'l, F j'); + $format = !empty($format) ? $format : (empty($date_info->mini) ? $format_with_year : $format_without_year); $title = date_format_date($date_info->min_date, 'custom', $format); - $date_arg = $date_info->year . '-' . date_pad($date_info->month) . '-' . date_pad($date_info->day); + $date_arg = $date_info->year; + $date_arg .= '-'; + $date_arg .= date_pad($date_info->month); + $date_arg .= '-'; + $date_arg .= date_pad($date_info->day); break; + case 'week': - $format = !empty($format) ? $format : (empty($date_info->mini) ? 'F j, Y' : 'F j'); - $title = t('Week of @date', array('@date' => date_format_date($date_info->min_date, 'custom', $format))); + $format = !empty($format) ? $format : (empty($date_info->mini) ? $format_with_year : $format_without_year); + $title = t('Week of @date', array( + '@date' => date_format_date($date_info->min_date, 'custom', $format), + )); $date_arg = $date_info->year . '-W' . date_pad($date_info->week); break; } diff --git a/sites/all/modules/contrib/fields/date/tests/date.test b/sites/all/modules/contrib/fields/date/tests/date.test index 3b8f35e..04b0644 100644 --- a/sites/all/modules/contrib/fields/date/tests/date.test +++ b/sites/all/modules/contrib/fields/date/tests/date.test @@ -5,8 +5,7 @@ * Test date UI. */ -class DateUITestCase extends DrupalWebTestCase { - protected $privileged_user; +class DateUITestCase extends DateFieldBasic { /** * @todo. @@ -23,14 +22,7 @@ class DateUITestCase extends DrupalWebTestCase { * @todo. */ public function setUp() { - // Load the date_api module. - parent::setUp('field', 'field_ui', 'date_api', 'date', 'date_popup', 'date_tools'); - - // Create and log in our privileged user. - $this->privileged_user = $this->drupalCreateUser( - array('administer content types', 'administer nodes', 'bypass node access', 'administer date tools') - ); - $this->drupalLogin($this->privileged_user); + parent::setUp(); variable_set('date_format_long', 'D, m/d/Y - H:i'); } @@ -39,82 +31,34 @@ class DateUITestCase extends DrupalWebTestCase { * @todo. */ public function testFieldUI() { - $edit = array(); - $edit['name'] = 'Story'; - $edit['type'] = 'story'; - $this->drupalPost('admin/structure/types/add', $edit, t('Save content type')); - $this->assertText('The content type Story has been added.', 'Content type added.'); + $label = 'Test'; + $current_year = date('Y'); - // Creates select list field stored as a date with default settings. - $this->createDateField($type = 'date', $widget = 'date_select'); - $edit = array(); - $this->drupalPost(NULL, $edit, t('Save field settings')); - $this->dateForm($options = 'select'); - $this->assertText('Thu, 10/07/2010 - 10:30', 'Found the correct date for a date field using the date_select widget.'); - $this->deleteDateField(); - // Creates text field stored as a date with default settings. - $this->createDateField($type = 'date', $widget = 'date_text'); - $edit = array(); - $this->drupalPost(NULL, $edit, t('Save field settings')); - $this->dateForm($options = 'text'); - $this->assertText('Thu, 10/07/2010 - 10:30', 'Found the correct date for a date field using the date_text widget.'); - $this->deleteDateField(); - // Creates popup field stored as a date with default settings. - $this->createDateField($type = 'date', $widget = 'date_popup'); - $edit = array(); - $this->drupalPost(NULL, $edit, t('Save field settings')); - $this->dateForm($options = 'popup'); - $this->assertText('Thu, 10/07/2010 - 10:30', 'Found the correct date for a date field using the date_popup widget.'); - $this->deleteDateField(); - // Creates select list field stored as a datestamp with default settings. - $this->createDateField($type = 'datestamp', $widget = 'date_select'); - $edit = array(); - $this->drupalPost(NULL, $edit, t('Save field settings')); - $this->dateForm($options = 'select'); - $this->assertText('Thu, 10/07/2010 - 10:30', 'Found the correct date for a datestamp field using the date_select widget.'); - $this->deleteDateField(); - // Creates text field stored as a datestamp with default settings. - $this->createDateField($type = 'datestamp', $widget = 'date_text'); - $edit = array(); - $this->drupalPost(NULL, $edit, t('Save field settings')); - $this->dateForm($options = 'text'); - $this->assertText('Thu, 10/07/2010 - 10:30', 'Found the correct date for a datestamp field using the date_text widget.'); - $this->deleteDateField(); - // Creates popup field stored as a datestamp with default settings. - $this->createDateField($type = 'datestamp', $widget = 'date_popup'); - $edit = array(); - $this->drupalPost(NULL, $edit, t('Save field settings')); - $this->dateForm($options = 'popup'); - $this->assertText('Thu, 10/07/2010 - 10:30', 'Found the correct date for a datestamp field using the date_popup widget.'); - $this->deleteDateField(); - // Creates select list field stored as a datetime with default settings. - $this->createDateField($type = 'datetime', $widget = 'date_select'); - $edit = array(); - $this->drupalPost(NULL, $edit, t('Save field settings')); - $this->dateForm($options = 'select'); - $this->assertText('Thu, 10/07/2010 - 10:30', 'Found the correct date for a datetime field using the date_select widget.'); - $this->deleteDateField(); - // Creates text field stored as a datetime with default settings. - $this->createDateField($type = 'datetime', $widget = 'date_text'); - $edit = array(); - $this->drupalPost(NULL, $edit, t('Save field settings')); - $this->dateForm($options = 'text'); - $this->assertText('Thu, 10/07/2010 - 10:30', 'Found the correct date for a datetime field using the date_text widget.'); - $this->deleteDateField(); - // Creates popup field stored as a datetime with default settings. - $this->createDateField($type = 'datetime', $widget = 'date_popup'); - $edit = array(); - $this->drupalPost(NULL, $edit, t('Save field settings')); - $this->dateForm($options = 'popup'); - $this->assertText('Thu, 10/07/2010 - 10:30', 'Found the correct date for a datetime field using the date_popup widget.'); - $this->deleteDateField(); + $field_types = array('date', 'datestamp', 'datetime'); + $widget_types = array('date_select', 'date_text', 'date_popup'); + + // Test widgets with default settings using every widget and field type. + foreach ($field_types as $field_type) { + foreach ($widget_types as $widget_type) { + $this->createDateField( + array( + 'label' => $label, + 'field_type' => $field_type, + 'widget_type' => $widget_type, + ) + ); + $this->dateForm($widget_type); + $this->assertText(format_string('10/07/!year - 10:30', array('!year' => $current_year)), 'Found the correct date for a date field using the ' . $widget_type . ' widget.'); + $this->deleteDateField($label); + } + } // Test timezone handling validation on the field settings form. - $this->createDateField($type = 'date', $widget = 'date_select'); + $this->createDateField(array('label' => $label, 'field_type' => 'date', 'widget_type' => 'date_select', 'granularity' => array('year', 'month', 'day'))); $edit = array('field[settings][granularity][hour]' => FALSE); - $this->drupalPost(NULL, $edit, t('Save field settings')); + $this->drupalPost('admin/structure/types/manage/story/fields/field_' . strtolower($label), $edit, t('Save settings')); $this->assertText("Dates without hours granularity must not use any timezone handling.", "Dates without hours granularity required to use timezone handling of 'none.'"); - $this->deleteDateField(); + $this->deleteDateField($label); } /** @@ -125,44 +69,23 @@ class DateUITestCase extends DrupalWebTestCase { $edit = array(); $edit['title'] = $this->randomName(8); $edit['body[und][0][value]'] = $this->randomName(16); - if ($options == 'select') { - $edit['field_test[und][0][value][year]'] = '2010'; + $current_year = date('Y'); + + if ($options == 'date_select') { + $edit['field_test[und][0][value][year]'] = $current_year; $edit['field_test[und][0][value][month]'] = '10'; $edit['field_test[und][0][value][day]'] = '7'; $edit['field_test[und][0][value][hour]'] = '10'; $edit['field_test[und][0][value][minute]'] = '30'; } - elseif ($options == 'text') { - $edit['field_test[und][0][value][date]'] = '10/07/2010 - 10:30'; + elseif ($options == 'date_text') { + $edit['field_test[und][0][value][date]'] = format_string('10/07/!year - 10:30', array('!year' => $current_year)); } - elseif ($options == 'popup') { - $edit['field_test[und][0][value][date]'] = '10/07/2010'; + elseif ($options == 'date_popup') { + $edit['field_test[und][0][value][date]'] = format_string('10/07/!year', array('!year' => $current_year)); $edit['field_test[und][0][value][time]'] = '10:30'; } $this->drupalPost('node/add/story', $edit, t('Save')); $this->assertText($edit['body[und][0][value]'], 'Test node has been created'); } - - /** - * @todo. - */ - function createDateField($type, $widget) { - $edit = array(); - $edit['fields[_add_new_field][label]'] = 'Test'; - $edit['fields[_add_new_field][field_name]'] = 'test'; - $edit['fields[_add_new_field][weight]'] = '-4'; - $edit['fields[_add_new_field][type]'] = $type; - $edit['fields[_add_new_field][widget_type]'] = $widget; - $this->drupalPost('admin/structure/types/manage/story/fields', $edit, t('Save')); - } - - /** - * @todo. - */ - function deleteDateField() { - $this->drupalGet('admin/structure/types/manage/story/fields'); - $this->clickLink('delete'); - $this->drupalPost(NULL, NULL, t('Delete')); - $this->assertText('The field Test has been deleted from the Story content type.', 'Removed date field.'); - } } diff --git a/sites/all/modules/contrib/fields/date/tests/date_api.test b/sites/all/modules/contrib/fields/date/tests/date_api.test index f50020c..03c9081 100644 --- a/sites/all/modules/contrib/fields/date/tests/date_api.test +++ b/sites/all/modules/contrib/fields/date/tests/date_api.test @@ -132,11 +132,11 @@ class DateAPITestCase extends DrupalWebTestCase { // Test week range with calendar weeks. variable_set('date_first_day', 0); variable_set('date_api_use_iso8601', FALSE); - $expected = '2008-01-27 to 2008-02-03'; + $expected = '2008-01-27 to 2008-02-02'; $result = date_week_range(5, 2008); $value = $result[0]->format(DATE_FORMAT_DATE) . ' to ' . $result[1]->format(DATE_FORMAT_DATE); $this->assertEqual($expected, $value, "Test calendar date_week_range(5, 2008): should be $expected, found $value."); - $expected = '2009-01-25 to 2009-02-01'; + $expected = '2009-01-25 to 2009-01-31'; $result = date_week_range(5, 2009); $value = $result[0]->format(DATE_FORMAT_DATE) . ' to ' . $result[1]->format(DATE_FORMAT_DATE); $this->assertEqual($expected, $value, "Test calendar date_week_range(5, 2009): should be $expected, found $value."); @@ -144,11 +144,11 @@ class DateAPITestCase extends DrupalWebTestCase { // And now with ISO weeks. variable_set('date_first_day', 1); variable_set('date_api_use_iso8601', TRUE); - $expected = '2008-01-28 to 2008-02-04'; + $expected = '2008-01-28 to 2008-02-03'; $result = date_week_range(5, 2008); $value = $result[0]->format(DATE_FORMAT_DATE) . ' to ' . $result[1]->format(DATE_FORMAT_DATE); $this->assertEqual($expected, $value, "Test ISO date_week_range(5, 2008): should be $expected, found $value."); - $expected = '2009-01-26 to 2009-02-02'; + $expected = '2009-01-26 to 2009-02-01'; $result = date_week_range(5, 2009); $value = $result[0]->format(DATE_FORMAT_DATE) . ' to ' . $result[1]->format(DATE_FORMAT_DATE); $this->assertEqual($expected, $value, "Test ISO date_week_range(5, 2009): should be $expected, found $value."); @@ -393,9 +393,31 @@ class DateAPITestCase extends DrupalWebTestCase { $input = '23 abc 2012'; $timezone = NULL; $format = 'd M Y'; - $date = new dateObject($input, $timezone, $format); + $date = @new dateObject($input, $timezone, $format); $this->assertNotEqual(count($date->errors), 0, '23 abc 2012 should be an invalid date'); + // Test Granularity. + $input = '2005-06-01 10:30:45'; + $timezone = NULL; + $format = 'Y-m-d H:i:s'; + + $date = new dateObject($input, $timezone, $format); + $date->removeGranularity('hour'); + $date->removeGranularity('second'); + $date->removeGranularity('minute'); + + $value = $date->format($format); + $expected = '2005-06-01'; + $this->assertEqual($expected, $value, "The date with removed granularity should be $expected, found $value."); + + $date->addGranularity('hour'); + $date->addGranularity('second'); + $date->addGranularity('minute'); + + $value = $date->format($format); + $expected = '2005-06-01 10:30:45'; + + $this->assertEqual($expected, $value, "The date with added granularity should be $expected, found $value."); } /** diff --git a/sites/all/modules/contrib/fields/date/tests/date_field.test b/sites/all/modules/contrib/fields/date/tests/date_field.test index b2a45a0..b8aad59 100644 --- a/sites/all/modules/contrib/fields/date/tests/date_field.test +++ b/sites/all/modules/contrib/fields/date/tests/date_field.test @@ -16,7 +16,7 @@ abstract class DateFieldBasic extends DrupalWebTestCase { // Create and log in our privileged user. $this->privileged_user = $this->drupalCreateUser( - array('administer content types', 'administer nodes', 'bypass node access', 'administer date tools') + array('administer content types', 'administer nodes', 'bypass node access', 'administer date tools', 'administer fields') ); $this->drupalLogin($this->privileged_user); @@ -52,7 +52,7 @@ abstract class DateFieldBasic extends DrupalWebTestCase { $repeat = !empty($repeat) ? $repeat : 0; $todate = !empty($todate) ? $todate : 'optional'; $widget_type = !empty($widget_type) ? $widget_type : 'date_select'; - $tz_handling = !empty($tz_handing) ? $tz_handling : 'site'; + $tz_handling = !empty($tz_handling) ? $tz_handling : 'site'; $granularity = !empty($granularity) ? $granularity : array('year', 'month', 'day', 'hour', 'minute'); $year_range = !empty($year_range) ? $year_range : '2010:+1'; $input_format = !empty($input_format) ? $input_format : date_default_format($widget_type); @@ -151,6 +151,123 @@ abstract class DateFieldBasic extends DrupalWebTestCase { } + /** + * Creates a date field from an array of settings values. + * + * All values have defaults, only need to specify values that need to be + * different. + */ + protected function createMultiDateField($values = array()) { + extract($values); + + $field_name = !empty($field_name) ? $field_name : 'field_test'; + $entity_type = !empty($entity_type) ? $entity_type : 'node'; + $bundle = !empty($bundle) ? $bundle : 'story'; + $label = !empty($label) ? $label : 'Test'; + $field_type = !empty($field_type) ? $field_type : 'datetime'; + $repeat = !empty($repeat) ? $repeat : 0; + $todate = !empty($todate) ? $todate : 'optional'; + $widget_type = !empty($widget_type) ? $widget_type : 'date_select'; + $this->verbose(!empty($tz_handling)); + $tz_handling = !empty($tz_handling) ? $tz_handling : 'site'; + $granularity = !empty($granularity) ? $granularity : array('year', 'month', 'day', 'hour', 'minute'); + $year_range = !empty($year_range) ? $year_range : '2010:+1'; + $input_format = !empty($input_format) ? $input_format : date_default_format($widget_type); + $input_format_custom = !empty($input_format_custom) ? $input_format_custom : ''; + $text_parts = !empty($text_parts) ? $text_parts : array(); + $increment = !empty($increment) ? $increment : 15; + $default_value = !empty($default_value) ? $default_value : 'now'; + $default_value2 = !empty($default_value2) ? $default_value2 : 'blank'; + $default_format = !empty($default_format) ? $default_format : 'long'; + $cache_enabled = !empty($cache_enabled); + $cache_count = !empty($cache_count) ? $cache_count : 4; + $cardinality = !empty($cardinality) ? $cardinality : 1; + + $field = array( + 'field_name' => $field_name, + 'type' => $field_type, + 'cardinality' => $cardinality, + 'settings' => array( + 'granularity' => $granularity, + 'tz_handling' => $tz_handling, + 'timezone_db' => date_get_timezone_db($tz_handling), + 'repeat' => $repeat, + 'todate' => $todate, + 'cache_enabled' => $cache_enabled, + 'cache_count' => $cache_count, + ), + ); + $instance = array( + 'entity_type' => $entity_type, + 'field_name' => $field_name, + 'label' => $label, + 'bundle' => $bundle, + // Move the date right below the title. + 'weight' => -4, + 'widget' => array( + 'type' => $widget_type, + // Increment for minutes and seconds, can be 1, 5, 10, 15, or 30. + 'settings' => array( + 'increment' => $increment, + // The number of years to go back and forward in drop-down year + // selectors. + 'year_range' => $year_range, + 'input_format' => $input_format, + 'input_format_custom' => $input_format_custom, + 'text_parts' => $text_parts, + 'label_position' => 'above', + 'repeat_collapsed' => 0, + ), + 'weight' => -4, + ), + 'settings' => array( + 'default_value' => $default_value, + 'default_value2' => $default_value2, + ), + ); + + $instance['display'] = array( + 'default' => array( + 'label' => 'above', + 'type' => 'date_default', + 'settings' => array( + 'format_type' => $default_format, + 'show_repeat_rule' => 'show', + 'multiple_number' => '', + 'multiple_from' => '', + 'multiple_to' => '', + 'fromto' => 'both', + ), + 'module' => 'date', + 'weight' => 0 , + ), + 'teaser' => array( + 'label' => 'above', + 'type' => 'date_default', + 'weight' => 0, + 'settings' => array( + 'format_type' => $default_format, + 'show_repeat_rule' => 'show', + 'multiple_number' => '', + 'multiple_from' => '', + 'multiple_to' => '', + 'fromto' => 'both', + ), + 'module' => 'date', + ), + ); + + $field = field_create_field($field); + $instance = field_create_instance($instance); + + field_info_cache_clear(TRUE); + field_cache_clear(TRUE); + + // Look at how the field got configured. + $this->drupalGet("admin/structure/types/manage/$bundle/fields/$field_name"); + $this->drupalGet("admin/structure/types/manage/$bundle/display"); + } + /** * @todo. */ diff --git a/sites/all/modules/contrib/fields/date/tests/date_form.test b/sites/all/modules/contrib/fields/date/tests/date_form.test new file mode 100644 index 0000000..56b89e4 --- /dev/null +++ b/sites/all/modules/contrib/fields/date/tests/date_form.test @@ -0,0 +1,30 @@ + t('Date Form test'), + 'description' => t('Test Date form functions.') , + 'group' => t('Date'), + ); + } + + public function setUp() { + // Load the date_api module. + parent::setUp('date_test'); + } + + /** + * Tests rendering of a date element in a form. + */ + public function testDateForm() { + $this->drupalGet('date-test/form'); + } + +} diff --git a/sites/all/modules/contrib/fields/date/date_migrate/date_migrate.test b/sites/all/modules/contrib/fields/date/tests/date_migrate.test similarity index 83% rename from sites/all/modules/contrib/fields/date/date_migrate/date_migrate.test rename to sites/all/modules/contrib/fields/date/tests/date_migrate.test index 400b2d2..ec2ae7d 100644 --- a/sites/all/modules/contrib/fields/date/date_migrate/date_migrate.test +++ b/sites/all/modules/contrib/fields/date/tests/date_migrate.test @@ -15,9 +15,10 @@ class DateMigrateExampleUnitTest extends DrupalWebTestCase { */ public static function getInfo() { return array( - 'name' => 'Date2 migration', - 'description' => 'Testing migration of date fields', - 'group' => 'Migrate', + 'name' => 'Date Migration', + 'description' => 'Test migration into date fields', + 'group' => 'Date', + 'dependencies' => array('migrate', 'features'), ); } @@ -25,7 +26,17 @@ class DateMigrateExampleUnitTest extends DrupalWebTestCase { * Declars the module dependencies for the test. */ function setUp() { - parent::setUp('migrate', 'features', 'date', 'date_repeat', 'date_repeat_field', 'date_migrate_example'); + parent::setUp('migrate', 'features', 'date', 'date_repeat', + 'date_repeat_field', 'date_migrate_example'); + // Make sure the migration is registered. + if (function_exists('migrate_static_registration')) { + // Migrate 2.6 and later + migrate_static_registration(); + } + else { + // Migrate 2.5 and earlier + migrate_get_module_apis(TRUE); + } } /** diff --git a/sites/all/modules/contrib/fields/date/tests/date_test/date_test.info b/sites/all/modules/contrib/fields/date/tests/date_test/date_test.info new file mode 100644 index 0000000..2f265fa --- /dev/null +++ b/sites/all/modules/contrib/fields/date/tests/date_test/date_test.info @@ -0,0 +1,14 @@ +name = "Date module tests" +description = "Support module for date related testing." +package = Date/Time +version = VERSION +core = 7.x +hidden = TRUE +dependencies[] = date + +; Information added by Drupal.org packaging script on 2017-04-07 +version = "7.x-2.10" +core = "7.x" +project = "date" +datestamp = "1491562090" + diff --git a/sites/all/modules/contrib/fields/date/tests/date_test/date_test.module b/sites/all/modules/contrib/fields/date/tests/date_test/date_test.module new file mode 100644 index 0000000..1854089 --- /dev/null +++ b/sites/all/modules/contrib/fields/date/tests/date_test/date_test.module @@ -0,0 +1,40 @@ + 'Test form with date element', + 'description' => "Form with date element to make form related tests", + 'page callback' => 'drupal_get_form', + 'page arguments' => array('date_test_sample_form'), + 'access arguments' => array('access content'), + 'type' => MENU_CALLBACK, + ); + return $items; +} + +/** + * Form callback. Generates a test form with date elements. + */ +function date_test_sample_form($form, &$form_state) { + $form['date_test_select'] = array( + '#type' => 'date_select', + '#title' => t('Sample from'), + '#date_format' => 'H:i:s a', + '#default_value' => array( + 'hour' => 7, + 'minute' => 0, + 'second' => 0, + 'ampm' => 'am' + ), + ); + + return $form; +} diff --git a/sites/all/modules/contrib/fields/date/tests/date_timezone.test b/sites/all/modules/contrib/fields/date/tests/date_timezone.test index ddbdcef..9f2905a 100644 --- a/sites/all/modules/contrib/fields/date/tests/date_timezone.test +++ b/sites/all/modules/contrib/fields/date/tests/date_timezone.test @@ -16,6 +16,16 @@ class DateTimezoneTestCase extends DateFieldBasic { ); } + public function setUp() { + parent::setUp(); + // Set the timezone explicitly. Otherwise the site's default timezone is + // used, which defaults to the server timezone when installing Drupal. This + // depends on the environment and is therefore uncertain. + // The Australia/Sydney timezone is chosen so all tests are run using an + // edge case scenario (UTC+10 and DST). + variable_set('date_default_timezone', 'Australia/Sydney'); + } + /** * @todo. */ @@ -23,7 +33,7 @@ class DateTimezoneTestCase extends DateFieldBasic { // Create a date fields with combinations of various timezone handling and // granularity. foreach (array('date', 'datestamp', 'datetime') as $field_type) { - foreach (array('site', 'none', 'date', 'user', 'utc') as $tz_handling) { + foreach (array('site', 'none', 'date', 'user', 'utc', 'Europe/Dublin') as $tz_handling) { foreach (array('year', 'month', 'day', 'hour', 'minute', 'second') as $max_granularity) { // Skip invalid combinations. if (in_array($max_granularity, array('year', 'month', 'day')) && $tz_handling != 'none') { @@ -50,6 +60,111 @@ class DateTimezoneTestCase extends DateFieldBasic { } } + /** + * Validates timezone handling with a multi-value date field. + */ + public function testMultiUserTimezone() { + // Create date fields with combinations of various types and granularity + // using the "Date's Timezone" strategy. + $field_type = 'datetime'; + $tz_handling = 'date'; + $max_granularity = 'minute'; + + // Create date field + $field_name = "field_test"; + $label = 'Test'; + $options = array( + 'label' => $label, + 'widget_type' => 'date_text', + 'field_name' => $field_name, + 'field_type' => $field_type, + 'input_format' => 'custom', + 'input_format_custom' => 'm/d/Y - H:i:s T', + 'cardinality' => 3, + 'tz_handling' => $tz_handling, + ); + $this->createMultiDateField($options); + + // Submit a date field form with multiple values + $this->dateMultiValueForm($field_name, $field_type, $max_granularity, $tz_handling); + + + $this->deleteDateField($label); + } + + /** + * Tests the submission of a date field's widget form when using unlimited + * cardinality + */ + public function dateMultiValueForm($field_name, $field_type, $max_granularity, $tz_handling) { + variable_set('date_format_long', 'D, m/d/Y - H:i:s T'); + + $edit = array(); + $should_be = array(); + $edit['title'] = $this->randomName(8); + $timezones = array('America/Chicago', 'America/Los_Angeles', 'America/New_York'); + + switch ($max_granularity) { + case 'hour': + $edit[$field_name . '[und][0][value][date]'] = '10/07/2010 - 10:30'; + $edit[$field_name . '[und][0][timezone][timezone]'] = 'America/Chicago'; + $should_be[0] = 'Thu, 10/07/2010 - 10 CDT'; + + $edit[$field_name . '[und][1][value][date]'] = '10/07/2010 - 10:30'; + $edit[$field_name . '[und][1][timezone][timezone]'] = 'America/Los_Angeles'; + $should_be[1] = 'Thu, 10/07/2010 - 10 PDT'; + + $edit[$field_name . '[und][2][value][date]'] = '10/07/2010 - 10:30'; + $edit[$field_name . '[und][2][timezone][timezone]'] = 'America/New_York'; + $should_be[2] = 'Thu, 10/07/2010 - 10 EDT'; + + break; + case 'minute': + $edit[$field_name . '[und][0][value][date]'] = '10/07/2010 - 10:30'; + $edit[$field_name . '[und][0][timezone][timezone]'] = 'America/Chicago'; + $should_be[0] = 'Thu, 10/07/2010 - 10:30 CDT'; + + $edit[$field_name . '[und][1][value][date]'] = '10/07/2010 - 10:30'; + $edit[$field_name . '[und][1][timezone][timezone]'] = 'America/Los_Angeles'; + $should_be[1] = 'Thu, 10/07/2010 - 10:30 PDT'; + + $edit[$field_name . '[und][2][value][date]'] = '10/07/2010 - 10:30'; + $edit[$field_name . '[und][2][timezone][timezone]'] = 'America/New_York'; + $should_be[2] = 'Thu, 10/07/2010 - 10:30 EDT'; + + break; + case 'second': + $edit[$field_name . '[und][0][value][date]'] = '10/07/2010 - 10:30'; + $edit[$field_name . '[und][0][timezone][timezone]'] = 'America/Chicago'; + $should_be[0] = 'Thu, 10/07/2010 - 10:30:30 CDT'; + + $edit[$field_name . '[und][1][value][date]'] = '10/07/2010 - 10:30'; + $edit[$field_name . '[und][1][timezone][timezone]'] = 'America/Los_Angeles'; + $should_be[1] = 'Thu, 10/07/2010 - 10:30:30 PDT'; + + $edit[$field_name . '[und][2][value][date]'] = '10/07/2010 - 10:30'; + $edit[$field_name . '[und][2][timezone][timezone]'] = 'America/New_York'; + $should_be[2] = 'Thu, 10/07/2010 - 10:30:30 EDT'; + break; + } + + $this->drupalPost('node/add/story', $edit, t('Save')); + $this->assertText($edit['title'], "Node has been created"); + + foreach ($should_be as $assertion) { + $this->assertText($assertion, "Found the correct date for a $field_type field using $max_granularity granularity with $tz_handling timezone handling."); + } + + // Goto the edit page and save the node again. + $node = $this->drupalGetNodeByTitle($edit['title']); + $this->drupalGet('node/' . $node->nid . '/edit'); + + // Re-assert the proper date timezones. + foreach ($timezones as $key => $timezone) { + $this->assertOptionSelected('edit-field-test-und-' . $key . '-timezone-timezone', $timezone, "Found the correct timezone $timezone for a $field_type field using $max_granularity granularity with $tz_handling timezone handling."); + } + } + /** * @todo. */ @@ -77,17 +192,32 @@ class DateTimezoneTestCase extends DateFieldBasic { case 'hour': $edit[$field_name . '[und][0][value][date]'] = '10/07/2010 - 10'; $edit[$field_name . '[und][0][value2][date]'] = '10/07/2010 - 11'; - $should_be = 'Thu, 10/07/2010 - 10 to Thu, 10/07/2010 - 11'; + if ($tz_handling == 'utc') { + $should_be = 'Thu, 10/07/2010 - 21 to Thu, 10/07/2010 - 22'; + } + else { + $should_be = 'Thu, 10/07/2010 - 10 to Thu, 10/07/2010 - 11'; + } break; case 'minute': $edit[$field_name . '[und][0][value][date]'] = '10/07/2010 - 10:30'; $edit[$field_name . '[und][0][value2][date]'] = '10/07/2010 - 11:30'; - $should_be = 'Thu, 10/07/2010 - 10:30 to 11:30'; + if ($tz_handling == 'utc') { + $should_be = 'Thu, 10/07/2010 - 21:30 to 22:30'; + } + else { + $should_be = 'Thu, 10/07/2010 - 10:30 to 11:30'; + } break; case 'second': $edit[$field_name . '[und][0][value][date]'] = '10/07/2010 - 10:30:30'; $edit[$field_name . '[und][0][value2][date]'] = '10/07/2010 - 11:30:30'; - $should_be = 'Thu, 10/07/2010 - 10:30:30 to 11:30:30'; + if ($tz_handling == 'utc') { + $should_be = 'Thu, 10/07/2010 - 21:30:30 to 22:30:30'; + } + else { + $should_be = 'Thu, 10/07/2010 - 10:30:30 to 11:30:30'; + } break; } $this->drupalPost('node/add/story', $edit, t('Save')); diff --git a/sites/all/modules/contrib/fields/date/tests/date_views_pager.test b/sites/all/modules/contrib/fields/date/tests/date_views_pager.test new file mode 100644 index 0000000..4f5dd97 --- /dev/null +++ b/sites/all/modules/contrib/fields/date/tests/date_views_pager.test @@ -0,0 +1,129 @@ + 'Date views pager skipping test', + 'description' => "Views date pager, option to skip empty pages test", + 'group' => 'Date', + ); + } + + /** + * Test setup actions. + */ + public function setUp() { + // Load the 'date_views', 'views', 'views_ui', 'ctools' modules. + parent::setUp('date_views', 'views', 'views_ui', 'ctools'); + // Set required permissions. + $permissions = array('administer views', 'administer site configuration'); + // Create admin user and login. + $admin_user = $this->drupalCreateUser($permissions); + $this->drupalLogin($admin_user); + + // Create a new view for test. + $view = new view(); + $view->name = 'test_date_pager'; + $view->description = ''; + $view->tag = 'default'; + $view->base_table = 'node'; + $view->human_name = 'test_date_pager'; + $view->core = 7; + $view->api_version = '3.0'; + $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */ + + /* Display: Master */ + $handler = $view->new_display('default', 'Master', 'default'); + $handler->display->display_options['title'] = 'test_date_pager'; + $handler->display->display_options['use_more_always'] = FALSE; + $handler->display->display_options['access']['type'] = 'perm'; + $handler->display->display_options['cache']['type'] = 'none'; + $handler->display->display_options['query']['type'] = 'views_query'; + $handler->display->display_options['exposed_form']['type'] = 'basic'; + $handler->display->display_options['pager']['type'] = 'date_views_pager'; + $handler->display->display_options['pager']['options']['skip_empty_pages'] = 1; + $handler->display->display_options['style_plugin'] = 'default'; + $handler->display->display_options['row_plugin'] = 'node'; + /* Field: Content: Title */ + $handler->display->display_options['fields']['title']['id'] = 'title'; + $handler->display->display_options['fields']['title']['table'] = 'node'; + $handler->display->display_options['fields']['title']['field'] = 'title'; + $handler->display->display_options['fields']['title']['label'] = ''; + $handler->display->display_options['fields']['title']['alter']['word_boundary'] = FALSE; + $handler->display->display_options['fields']['title']['alter']['ellipsis'] = FALSE; + /* Sort criterion: Content: Post date */ + $handler->display->display_options['sorts']['created']['id'] = 'created'; + $handler->display->display_options['sorts']['created']['table'] = 'node'; + $handler->display->display_options['sorts']['created']['field'] = 'created'; + $handler->display->display_options['sorts']['created']['order'] = 'DESC'; + /* Contextual filter: Date: Date (node) */ + $handler->display->display_options['arguments']['date_argument']['id'] = 'date_argument'; + $handler->display->display_options['arguments']['date_argument']['table'] = 'node'; + $handler->display->display_options['arguments']['date_argument']['field'] = 'date_argument'; + $handler->display->display_options['arguments']['date_argument']['default_action'] = 'default'; + $handler->display->display_options['arguments']['date_argument']['default_argument_type'] = 'date'; + $handler->display->display_options['arguments']['date_argument']['summary']['format'] = 'default_summary'; + $handler->display->display_options['arguments']['date_argument']['granularity'] = 'hour'; + $handler->display->display_options['arguments']['date_argument']['date_fields'] = array( + 'node.created' => 'node.created', + ); + /* Filter criterion: Content: Published */ + $handler->display->display_options['filters']['status']['id'] = 'status'; + $handler->display->display_options['filters']['status']['table'] = 'node'; + $handler->display->display_options['filters']['status']['field'] = 'status'; + $handler->display->display_options['filters']['status']['value'] = 1; + $handler->display->display_options['filters']['status']['group'] = 1; + $handler->display->display_options['filters']['status']['expose']['operator'] = FALSE; + + /* Display: Page */ + $handler = $view->new_display('page', 'Page', 'page_1'); + $handler->display->display_options['path'] = 'test_date_pager'; + + $view->save(); + } + + /** + * Test pager skipping. + */ + public function testPagerSkipping() { + // Go to view admin page. + $this->drupalGet('admin/structure/views/view/display/test_date_pager/edit'); + // Go to pager options. + $this->drupalGet('admin/structure/views/nojs/display/test_date_pager/default/pager_options'); + // Check if "Skip empty pages" text - exist. + $this->assertText('Skip empty pages'); + // Check if field and it's value is correct. + $this->assertFieldByName('pager_options[skip_empty_pages]', '1'); + // Go back to view admin page. + $this->drupalGet('admin/structure/views/view/display/test_date_pager/edit'); + // Check if pager on empty page are gone. + $this->assertNoText('« Prev', 'Previous pager does not exist'); + $this->assertNoText('Next »', 'Next pager does not exist'); + } + + /** + * Test the view page has no PHP warnings. + */ + public function testPagerWarning() { + $this->drupalCreateNode(array('type' => 'blog')); + // Set pager to skip empty pages. + $edit = array( + 'pager_options[skip_empty_pages]' => FALSE, + ); + $this->drupalPost('admin/structure/views/nojs/display/test_date_pager/default/pager_options', $edit, t('Apply')); + // Save the view. + $this->drupalPost('admin/structure/views/view/test_date_pager/edit', array(), t('Save')); + + // Visit view page. This will throw error, if any PHP warnings or errors. + $this->drupalGet('test_date_pager'); + } + +} diff --git a/sites/all/modules/contrib/fields/date/tests/date_views_popup.test b/sites/all/modules/contrib/fields/date/tests/date_views_popup.test new file mode 100644 index 0000000..e3d4306 --- /dev/null +++ b/sites/all/modules/contrib/fields/date/tests/date_views_popup.test @@ -0,0 +1,106 @@ + 'Date Views - Popup Test', + 'description' => 'Tests date popup in Views', + 'group' => 'Date', + ); + } + + /** + * Test setup actions. + */ + public function setUp() { + parent::setUp(); + // Load the 'date_popup', 'date_views', 'views', 'views_ui', 'ctools' modules. + $modules = array('date_popup', 'date_views', 'views', 'views_ui', 'ctools'); + $success = module_enable($modules, TRUE); + $this->assertTrue($success, t('Enabled modules: %modules', array('%modules' => implode(', ', $modules)))); + + // Reset/rebuild all data structures after enabling the modules. + $this->resetAll(); + + // Create a date field. + $field_name = "field_test_date_popup"; + $label = 'Test'; + $options = array( + 'label' => 'Test', + 'widget_type' => 'date_popup', + 'field_name' => $field_name, + 'field_type' => 'datetime', + 'input_format' => 'm/d/Y - H:i', + ); + $this->createDateField($options); + + // Set required permissions. + $permissions = array('administer views', 'administer site configuration'); + // Create admin user and login. + $admin_user = $this->drupalCreateUser($permissions); + $this->drupalLogin($admin_user); + + // Create the view. + $view = new view(); + $view->name = 'test_date_popup'; + $view->description = ''; + $view->tag = 'default'; + $view->base_table = 'node'; + $view->human_name = 'Test date_popup'; + $view->core = 7; + $view->api_version = '3.0'; + $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */ + + /* Display: Master */ + $handler = $view->new_display('default', 'Master', 'default'); + $handler->display->display_options['title'] = 'test_date_popup_page'; + $handler->display->display_options['use_more_always'] = FALSE; + $handler->display->display_options['access']['type'] = 'perm'; + $handler->display->display_options['cache']['type'] = 'none'; + $handler->display->display_options['query']['type'] = 'views_query'; + $handler->display->display_options['exposed_form']['type'] = 'basic'; + $handler->display->display_options['pager']['type'] = 'none'; + $handler->display->display_options['pager']['options']['offset'] = '0'; + $handler->display->display_options['style_plugin'] = 'default'; + $handler->display->display_options['row_plugin'] = 'node'; + /* Field: Content: Title */ + $handler->display->display_options['fields']['title']['id'] = 'title'; + $handler->display->display_options['fields']['title']['table'] = 'node'; + $handler->display->display_options['fields']['title']['field'] = 'title'; + $handler->display->display_options['fields']['title']['label'] = ''; + $handler->display->display_options['fields']['title']['alter']['word_boundary'] = FALSE; + $handler->display->display_options['fields']['title']['alter']['ellipsis'] = FALSE; + /* Filter criterion: Content: test_date_popup (field_test_date_popup) */ + $handler->display->display_options['filters']['field_test_date_popup_value']['id'] = 'field_test_date_popup_value'; + $handler->display->display_options['filters']['field_test_date_popup_value']['table'] = 'field_data_field_test_date_popup'; + $handler->display->display_options['filters']['field_test_date_popup_value']['field'] = 'field_test_date_popup_value'; + $handler->display->display_options['filters']['field_test_date_popup_value']['exposed'] = TRUE; + $handler->display->display_options['filters']['field_test_date_popup_value']['expose']['operator_id'] = 'field_test_date_popup_value_op'; + $handler->display->display_options['filters']['field_test_date_popup_value']['expose']['label'] = 'test_date_popup (field_test_date_popup)'; + $handler->display->display_options['filters']['field_test_date_popup_value']['expose']['operator'] = 'field_test_date_popup_value_op'; + $handler->display->display_options['filters']['field_test_date_popup_value']['expose']['identifier'] = 'field_test_date_popup_value'; + $handler->display->display_options['filters']['field_test_date_popup_value']['form_type'] = 'date_popup'; + + /* Display: Page */ + $handler = $view->new_display('page', 'Page', 'page'); + $handler->display->display_options['path'] = 'test-date-popup'; + + $view->save(); + } + + /** + * Test date popup. + */ + public function testDatePopup() { + // Go to view page. + $this->drupalGet('test-date-popup'); + } +} diff --git a/sites/all/modules/contrib/fields/entityreference/entityreference.devel_generate.inc b/sites/all/modules/contrib/fields/entityreference/entityreference.devel_generate.inc index eee767c..fab46dd 100644 --- a/sites/all/modules/contrib/fields/entityreference/entityreference.devel_generate.inc +++ b/sites/all/modules/contrib/fields/entityreference/entityreference.devel_generate.inc @@ -19,8 +19,14 @@ function _entityreference_devel_generate($object, $field, $instance, $bundle) { // Get all the entity that are referencable here. $referencable_entity = entityreference_get_selection_handler($field, $instance)->getReferencableEntities(); if (is_array($referencable_entity) && !empty($referencable_entity)) { - // Get a random key. - $object_field['target_id'] = array_rand($referencable_entity); + // $referencable_entity is keyed by bundle type. + $random_bundle = array_rand($referencable_entity); + if (!empty($random_bundle)) { + $target_id = array_rand($referencable_entity[$random_bundle]); + if (!empty($referencable_entity[$random_bundle][$target_id])) { + $object_field['target_id'] = $target_id; + } + } } return $object_field; } diff --git a/sites/all/modules/contrib/fields/entityreference/entityreference.feeds.inc b/sites/all/modules/contrib/fields/entityreference/entityreference.feeds.inc index d98a4ce..3da1479 100644 --- a/sites/all/modules/contrib/fields/entityreference/entityreference.feeds.inc +++ b/sites/all/modules/contrib/fields/entityreference/entityreference.feeds.inc @@ -15,14 +15,42 @@ function entityreference_feeds_processor_targets_alter(&$targets, $entity_type, foreach (field_info_instances($entity_type, $bundle_name) as $name => $instance) { $info = field_info_field($name); if ($info['type'] == 'entityreference') { + // We don't use ":guid" in key, not to break existing configurations. $targets[$name] = array( - 'name' => check_plain($instance['label']), + 'name' => check_plain($instance['label'] . t(' (Entity reference by Feeds GUID)')), 'callback' => 'entityreference_feeds_set_target', - 'description' => t('The field instance @label of @id', array( + 'description' => t('The field instance @label of @id matched by Feeds GUID.', array( '@label' => $instance['label'], '@id' => $name, )), ); + $targets[$name . ':url'] = array( + 'name' => check_plain($instance['label'] . t(' (Entity reference by Feeds URL)')), + 'callback' => 'entityreference_feeds_set_target', + 'description' => t('The field instance @label of @id matched by Feeds URL.', array( + '@label' => $instance['label'], + '@id' => $name, + )), + 'real_target' => $name, + ); + $targets[$name . ':etid'] = array( + 'name' => check_plain($instance['label'] . t(' (Entity reference by Entity ID)')), + 'callback' => 'entityreference_feeds_set_target', + 'description' => t('The field instance @label of @id matched by Entity ID.', array( + '@label' => $instance['label'], + '@id' => $name, + )), + 'real_target' => $name, + ); + $targets[$name . ':label'] = array( + 'name' => check_plain($instance['label'] . t(' (Entity reference by Entity label)')), + 'callback' => 'entityreference_feeds_set_target', + 'description' => t('The field instance @label of @id matched by Entity label.', array( + '@label' => $instance['label'], + '@id' => $name, + )), + 'real_target' => $name, + ); } } } @@ -42,12 +70,8 @@ function entityreference_feeds_processor_targets_alter(&$targets, $entity_type, * The target key on $entity to map to. * @param $value * The value to map. MUST be an array. - * @param $mapping - * Array of mapping settings for current value. - * @param $input_format - * TRUE if an input format should be applied. */ -function entityreference_feeds_set_target($source, $entity, $target, $value, $mapping, $input_format = FALSE) { +function entityreference_feeds_set_target($source, $entity, $target, $value) { // Don't do anything if we weren't given any data. if (empty($value)) { @@ -62,8 +86,19 @@ function entityreference_feeds_set_target($source, $entity, $target, $value, $ma $values = array($value); } + // Determine the field we are matching against. + if (strpos($target, ':') === FALSE) { + $match_key = 'guid'; + } + else { + list($target, $match_key) = explode(':', $target, 2); + } + // Get some useful field information. $info = field_info_field($target); + if ($match_key == 'label') { + $handler = entityreference_get_selection_handler($info); + } // Set the language of the field depending on the mapping. $language = isset($mapping['language']) ? $mapping['language'] : LANGUAGE_NONE; @@ -75,13 +110,32 @@ function entityreference_feeds_set_target($source, $entity, $target, $value, $ma // Only process if this value was set for this instance. if ($value) { - - // Fetch the entity ID resulting from the mapping table look-up. - $entity_id = db_query( - 'SELECT entity_id FROM {feeds_item} WHERE guid = :guid', - array(':guid' => $value) - )->fetchField(); - + switch ($match_key) { + case 'guid': + case 'url': + // Fetch the entity ID resulting from the mapping table look-up. + $entity_id = db_select('feeds_item', 'fi') + ->fields('fi', array('entity_id')) + ->condition($match_key, $value,'=') + ->execute() + ->fetchField(); + break; + case 'etid': + $entity_id = $value; + break; + case 'label': + $options = $handler->getReferencableEntities($value, '='); + if ($options) { + $options = reset($options); + $etids = array_keys($options); + // Use the first matching entity. + $entity_id = reset($etids); + } + else { + $entity_id = NULL; + } + break; + } /* * Only add a reference to an existing entity ID if there exists a * mapping between it and the provided GUID. In cases where no such @@ -106,6 +160,7 @@ function entityreference_feeds_set_target($source, $entity, $target, $value, $ma * this opportunity later, we need to destroy the hash. */ unset($entity->feeds_item->hash); + $source->log('entityreference', t('No existing entity found for entity @source_id entityreference to source entity @value', array('@source_id' => $entity->feeds_item->entity_id, '@value' => $value))); } } diff --git a/sites/all/modules/contrib/fields/entityreference/entityreference.info b/sites/all/modules/contrib/fields/entityreference/entityreference.info index 3aa51ab..5b2a0f7 100644 --- a/sites/all/modules/contrib/fields/entityreference/entityreference.info +++ b/sites/all/modules/contrib/fields/entityreference/entityreference.info @@ -1,18 +1,23 @@ name = Entity Reference description = Provides a field that can reference other entities. -core = 7.x package = Fields +core = 7.x + dependencies[] = entity dependencies[] = ctools +test_dependencies[] = feeds +test_dependencies[] = views + ; Migrate handler. files[] = entityreference.migrate.inc -; Our plugins interfaces and abstract implementations. +; Plugins interfaces and abstract implementations. files[] = plugins/selection/abstract.inc files[] = plugins/selection/views.inc files[] = plugins/behavior/abstract.inc +; Views integration. files[] = views/entityreference_plugin_display.inc files[] = views/entityreference_plugin_style.inc files[] = views/entityreference_plugin_row_fields.inc @@ -21,10 +26,12 @@ files[] = views/entityreference_plugin_row_fields.inc files[] = tests/entityreference.handlers.test files[] = tests/entityreference.taxonomy.test files[] = tests/entityreference.admin.test +files[] = tests/entityreference.feeds.test +files[] = tests/entityreference.entity_translation.test -; Information added by drupal.org packaging script on 2012-11-18 -version = "7.x-1.0" +; Information added by Drupal.org packaging script on 2017-08-16 +version = "7.x-1.5" core = "7.x" project = "entityreference" -datestamp = "1353230808" +datestamp = "1502895850" diff --git a/sites/all/modules/contrib/fields/entityreference/entityreference.install b/sites/all/modules/contrib/fields/entityreference/entityreference.install index 4e248ef..ce61018 100644 --- a/sites/all/modules/contrib/fields/entityreference/entityreference.install +++ b/sites/all/modules/contrib/fields/entityreference/entityreference.install @@ -41,6 +41,7 @@ function entityreference_field_schema($field) { } // Invoke the behaviors to allow them to change the schema. + module_load_include('module', 'entityreference'); foreach (entityreference_get_behavior_handlers($field) as $handler) { $handler->schema_alter($schema, $field); } @@ -161,4 +162,29 @@ function entityreference_update_7002() { 'not null' => TRUE, )); } -} \ No newline at end of file +} + +/** + * Implements hook_update_N(). + * + * Remove duplicate rows in the taxonomy_index table. + */ +function entityreference_update_7100() { + if (db_table_exists('taxonomy_index')) { + if (db_table_exists('taxonomy_index_tmp')) { + db_drop_table('taxonomy_index_tmp'); + } + + $tx_schema = drupal_get_schema('taxonomy_index'); + db_create_table('taxonomy_index_tmp', $tx_schema); + $select = db_select('taxonomy_index', 'tx'); + $select->fields('tx', array('nid', 'tid')); + $select->groupBy('tx.nid'); + $select->groupBy('tx.tid'); + $select->addExpression('MAX(sticky)', 'sticky'); + $select->addExpression('MAX(created)', 'created'); + db_insert('taxonomy_index_tmp')->from($select)->execute(); + db_drop_table('taxonomy_index'); + db_rename_table('taxonomy_index_tmp', 'taxonomy_index'); + } +} diff --git a/sites/all/modules/contrib/fields/entityreference/entityreference.migrate.inc b/sites/all/modules/contrib/fields/entityreference/entityreference.migrate.inc index 79c8f79..35737b5 100644 --- a/sites/all/modules/contrib/fields/entityreference/entityreference.migrate.inc +++ b/sites/all/modules/contrib/fields/entityreference/entityreference.migrate.inc @@ -1,21 +1,27 @@ 2, - 'field_handlers' => array('MigrateEntityReferenceFieldHandler'), + 'field handlers' => array('MigrateEntityReferenceFieldHandler'), ); } +/** + * Extended class for handling entityreference fields. + */ class MigrateEntityReferenceFieldHandler extends MigrateSimpleFieldHandler { + + /** + * Constructor. + */ public function __construct() { parent::__construct(array( 'value_key' => 'target_id', diff --git a/sites/all/modules/contrib/fields/entityreference/entityreference.module b/sites/all/modules/contrib/fields/entityreference/entityreference.module index 5f03c5b..c0c9f58 100644 --- a/sites/all/modules/contrib/fields/entityreference/entityreference.module +++ b/sites/all/modules/contrib/fields/entityreference/entityreference.module @@ -1,5 +1,12 @@ array( + 'variables' => array('label' => NULL, 'item' => NULL, 'settings' => NULL, 'uri' => NULL), + ), + 'entityreference_entity_id' => array( + 'variables' => array('item' => NULL, 'settings' => NULL), + ), + ); +} + /** * Implements hook_menu(). */ @@ -163,7 +184,7 @@ function entityreference_get_behavior_handlers($field, $instance = NULL) { /** * Get the behavior handler for a given entityreference field and instance. * - * @param $handler + * @param $behavior * The behavior handler name. */ function _entityreference_get_behavior_handler($behavior) { @@ -220,13 +241,15 @@ function entityreference_field_validate($entity_type, $entity, $field, $instance if ($ids) { $valid_ids = entityreference_get_selection_handler($field, $instance, $entity_type, $entity)->validateReferencableEntities(array_keys($ids)); - $invalid_entities = array_diff_key($ids, array_flip($valid_ids)); - if ($invalid_entities) { - foreach ($invalid_entities as $id => $delta) { - $errors[$field['field_name']][$langcode][$delta][] = array( - 'error' => 'entityreference_invalid_entity', - 'message' => t('The referenced entity (@type: @id) is invalid.', array('@type' => $field['settings']['target_type'], '@id' => $id)), - ); + if (!empty($valid_ids)) { + $invalid_entities = array_diff_key($ids, array_flip($valid_ids)); + if ($invalid_entities) { + foreach ($invalid_entities as $id => $delta) { + $errors[$field['field_name']][$langcode][$delta][] = array( + 'error' => 'entityreference_invalid_entity', + 'message' => t('The referenced entity (@type: @id) is invalid.', array('@type' => $field['settings']['target_type'], '@id' => $id)), + ); + } } } } @@ -398,6 +421,9 @@ function entityreference_field_settings_form($field, $instance, $has_data) { return $form; } +/** + * Callback for custom element processing. + */ function _entityreference_field_settings_process($form, $form_state) { $field = isset($form_state['entityreference']['field']) ? $form_state['entityreference']['field'] : $form['#field']; $instance = isset($form_state['entityreference']['instance']) ? $form_state['entityreference']['instance'] : $form['#instance']; @@ -479,11 +505,17 @@ function _entityreference_field_settings_process($form, $form_state) { return $form; } +/** + * Custom callback for ajax processing. + */ function _entityreference_field_settings_ajax_process($form, $form_state) { _entityreference_field_settings_ajax_process_element($form, $form); return $form; } +/** + * Helper function for custom ajax processing. + */ function _entityreference_field_settings_ajax_process_element(&$element, $main_form) { if (isset($element['#ajax']) && $element['#ajax'] === TRUE) { $element['#ajax'] = array( @@ -498,6 +530,9 @@ function _entityreference_field_settings_ajax_process_element(&$element, $main_f } } +/** + * Custom callback for element processing. + */ function _entityreference_form_process_merge_parent($element) { $parents = $element['#parents']; array_pop($parents); @@ -505,11 +540,17 @@ function _entityreference_form_process_merge_parent($element) { return $element; } +/** + * Helper function to remove blank elements. + */ function _entityreference_element_validate_filter(&$element, &$form_state) { $element['#value'] = array_filter($element['#value']); form_set_value($element, $element['#value'], $form_state); } +/** + * Implements hook_validate(). + */ function _entityreference_field_settings_validate($form, &$form_state) { // Store the new values in the form state. $field = $form['#field']; @@ -545,6 +586,9 @@ function entityreference_field_instance_settings_form($field, $instance) { return $form; } +/** + * Implements hook_field_settings_form(). + */ function _entityreference_field_instance_settings_form($form, $form_state) { $field = isset($form_state['entityreference']['field']) ? $form_state['entityreference']['field'] : $form['#field']; $instance = isset($form_state['entityreference']['instance']) ? $form_state['entityreference']['instance'] : $form['#instance']; @@ -562,6 +606,9 @@ function _entityreference_field_instance_settings_form($form, $form_state) { return $form; } +/** + * Implements hook_validate(). + */ function _entityreference_field_instance_settings_validate($form, &$form_state) { // Store the new values in the form state. $instance = $form['#instance']; @@ -791,6 +838,11 @@ function entityreference_query_entityreference_alter(QueryAlterableInterface $qu * Implements hook_field_widget_form(). */ function entityreference_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) { + // Ensure that the entity target type exists before displaying the widget. + $entity_info = entity_get_info($field['settings']['target_type']); + if (empty($entity_info)) { + return; + } $entity_type = $instance['entity_type']; $entity = isset($element['#entity']) ? $element['#entity'] : NULL; $handler = entityreference_get_selection_handler($field, $instance, $entity_type, $entity); @@ -813,7 +865,9 @@ function entityreference_field_widget_form(&$form, &$form_state, $field, $instan // Build an array of entities ID. foreach ($items as $item) { - $entity_ids[] = $item['target_id']; + if (isset($item['target_id'])) { + $entity_ids[] = $item['target_id']; + } } // Load those entities and loop through them to extract their labels. @@ -874,6 +928,9 @@ function entityreference_field_widget_form(&$form, &$form_state, $field, $instan } } +/** + * Implements hook_validate(). + */ function _entityreference_autocomplete_validate($element, &$form_state, $form) { // If a value was entered into the autocomplete... $value = ''; @@ -898,6 +955,9 @@ function _entityreference_autocomplete_validate($element, &$form_state, $form) { form_set_value($element, $value, $form_state); } +/** + * Implements hook_validate(). + */ function _entityreference_autocomplete_tags_validate($element, &$form_state, $form) { $value = array(); // If a value was entered into the autocomplete... @@ -944,7 +1004,8 @@ function entityreference_field_widget_error($element, $error) { * The entity type. * @param $bundle_name * The bundle name. - * @return + * + * @return bool * True if user can access this menu item. */ function entityreference_autocomplete_access_callback($type, $field_name, $entity_type, $bundle_name) { @@ -975,6 +1036,19 @@ function entityreference_autocomplete_access_callback($type, $field_name, $entit * The label of the entity to query by. */ function entityreference_autocomplete_callback($type, $field_name, $entity_type, $bundle_name, $entity_id = '', $string = '') { + // If the request has a '/' in the search text, then the menu system will have + // split it into multiple arguments and $string will only be a partial. + // We want to make sure we recover the intended $string. + $args = func_get_args(); + // Shift off the $type, $field_name, $entity_type, + // $bundle_name, and $entity_id args. + array_shift($args); + array_shift($args); + array_shift($args); + array_shift($args); + array_shift($args); + $string = implode('/', $args); + $field = field_info_field($field_name); $instance = field_info_instance($entity_type, $field_name, $bundle_name); @@ -1003,11 +1077,14 @@ function entityreference_autocomplete_callback($type, $field_name, $entity_type, */ function entityreference_autocomplete_callback_get_matches($type, $field, $instance, $entity_type, $entity_id = '', $string = '') { $matches = array(); + $prefix = ''; $entity = NULL; if ($entity_id !== 'NULL') { $entity = entity_load_single($entity_type, $entity_id); - if (!$entity || !entity_access('view', $entity_type, $entity)) { + $has_view_access = (entity_access('view', $entity_type, $entity) !== FALSE); + $has_update_access = (entity_access('update', $entity_type, $entity) !== FALSE); + if (!$entity || !($has_view_access || $has_update_access)) { return MENU_ACCESS_DENIED; } } @@ -1015,7 +1092,8 @@ function entityreference_autocomplete_callback_get_matches($type, $field, $insta $handler = entityreference_get_selection_handler($field, $instance, $entity_type, $entity); if ($type == 'tags') { - // The user enters a comma-separated list of tags. We only autocomplete the last tag. + // The user enters a comma-separated list of tags. + // We only autocomplete the last tag. $tags_typed = drupal_explode_tags($string); $tag_last = drupal_strtolower(array_pop($tags_typed)); if (!empty($tag_last)) { @@ -1024,19 +1102,22 @@ function entityreference_autocomplete_callback_get_matches($type, $field, $insta } else { // The user enters a single tag. - $prefix = ''; $tag_last = $string; } if (isset($tag_last)) { // Get an array of matching entities. $entity_labels = $handler->getReferencableEntities($tag_last, $instance['widget']['settings']['match_operator'], 10); - + $denied_label = t(ENTITYREFERENCE_DENIED); // Loop through the products and convert them into autocomplete output. foreach ($entity_labels as $values) { foreach ($values as $entity_id => $label) { + // Never autocomplete entities that aren't accessible. + if ($label == $denied_label) { + continue; + } $key = "$label ($entity_id)"; - // Strip things like starting/trailing white spaces, line breaks and tags. + // Strip starting/trailing white spaces, line breaks and tags. $key = preg_replace('/\s\s+/', ' ', str_replace("\n", '', trim(decode_entities(strip_tags($key))))); // Names containing commas or quotes must be wrapped in quotes. if (strpos($key, ',') !== FALSE || strpos($key, '"') !== FALSE) { @@ -1050,6 +1131,32 @@ function entityreference_autocomplete_callback_get_matches($type, $field, $insta drupal_json_output($matches); } + /** + * Introspects field and instance settings, and determines the correct settings + * for the functioning of the formatter. + * + * Settings: + * - entity_type - The entity_type being loaded. + * - column - The name of the ref. field column that stores the entity id. + */ +function entityreference_field_type_settings($field) { + $settings = array( + 'entity_type' => NULL, + 'column' => NULL, + ); + + if ($field['type'] == 'entityreference') { + $settings['entity_type'] = $field['settings']['target_type']; + $settings['column'] = 'target_id'; + } + elseif ($field['type'] == 'taxonomy_term_reference') { + $settings['entity_type'] = 'taxonomy_term'; + $settings['column'] = 'tid'; + } + + return $settings; +} + /** * Implements hook_field_formatter_info(). */ @@ -1061,6 +1168,7 @@ function entityreference_field_formatter_info() { 'field types' => array('entityreference'), 'settings' => array( 'link' => FALSE, + 'bypass_access' => FALSE, ), ), 'entityreference_entity_id' => array( @@ -1071,10 +1179,11 @@ function entityreference_field_formatter_info() { 'entityreference_entity_view' => array( 'label' => t('Rendered entity'), 'description' => t('Display the referenced entities rendered by entity_view().'), - 'field types' => array('entityreference'), + 'field types' => array('entityreference', 'taxonomy_term_reference'), 'settings' => array( - 'view_mode' => '', + 'view_mode' => 'default', 'links' => TRUE, + 'use_content_language' => TRUE, ), ), ); @@ -1086,8 +1195,17 @@ function entityreference_field_formatter_info() { function entityreference_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) { $display = $instance['display'][$view_mode]; $settings = $display['settings']; + $field_type_settings = entityreference_field_type_settings($field); + $element = array(); if ($display['type'] == 'entityreference_label') { + $element['bypass_access'] = array( + '#title' => t('Show entity labels regardless of user access'), + '#description' => t("All entities in the field will be shown, without checking them for access. If the 'Link' setting is also enabled, an entity which the user does not have access to view will show without a link."), + '#type' => 'checkbox', + '#default_value' => $settings['bypass_access'], + ); + $element['link'] = array( '#title' => t('Link label to the referenced entity'), '#type' => 'checkbox', @@ -1096,28 +1214,33 @@ function entityreference_field_formatter_settings_form($field, $instance, $view_ } if ($display['type'] == 'entityreference_entity_view') { - $entity_info = entity_get_info($field['settings']['target_type']); - $options = array(); + $entity_info = entity_get_info($field_type_settings['entity_type']); + $options = array('default' => t('Default')); if (!empty($entity_info['view modes'])) { foreach ($entity_info['view modes'] as $view_mode => $view_mode_settings) { $options[$view_mode] = $view_mode_settings['label']; } } - if (count($options) > 1) { - $element['view_mode'] = array( - '#type' => 'select', - '#options' => $options, - '#title' => t('View mode'), - '#default_value' => $settings['view_mode'], - ); - } + $element['view_mode'] = array( + '#type' => 'select', + '#options' => $options, + '#title' => t('View mode'), + '#default_value' => $settings['view_mode'], + '#access' => count($options) > 1, + ); $element['links'] = array( '#type' => 'checkbox', '#title' => t('Show links'), '#default_value' => $settings['links'], ); + + $element['use_content_language'] = array( + '#type' => 'checkbox', + '#title' => t('Use current content language'), + '#default_value' => $settings['use_content_language'], + ); } return $element; @@ -1129,17 +1252,24 @@ function entityreference_field_formatter_settings_form($field, $instance, $view_ function entityreference_field_formatter_settings_summary($field, $instance, $view_mode) { $display = $instance['display'][$view_mode]; $settings = $display['settings']; + $field_type_settings = entityreference_field_type_settings($field); $summary = array(); if ($display['type'] == 'entityreference_label') { $summary[] = $settings['link'] ? t('Link to the referenced entity') : t('No link'); + $summary[] = $settings['bypass_access'] ? t('Show labels regardless of access') : t('Respect entity access for label visibility'); } if ($display['type'] == 'entityreference_entity_view') { - $entity_info = entity_get_info($field['settings']['target_type']); - $summary[] = t('Rendered as @mode', array('@mode' => isset($entity_info['view modes'][$settings['view_mode']]['label']) ? $entity_info['view modes'][$settings['view_mode']]['label'] : $settings['view_mode'])); + $entity_info = entity_get_info($field_type_settings['entity_type']); + $view_mode_label = $settings['view_mode'] == 'default' ? t('Default') : $settings['view_mode']; + if (isset($entity_info['view modes'][$settings['view_mode']]['label'])) { + $view_mode_label = $entity_info['view modes'][$settings['view_mode']]['label']; + } + $summary[] = t('Rendered as @mode', array('@mode' => $view_mode_label)); $summary[] = !empty($settings['links']) ? t('Display links') : t('Do not display links'); + $summary[] = !empty($settings['use_content_language']) ? t('Use current content language') : t('Use field language'); } return implode('
    ', $summary); @@ -1149,19 +1279,22 @@ function entityreference_field_formatter_settings_summary($field, $instance, $vi * Implements hook_field_formatter_prepare_view(). */ function entityreference_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $displays) { + $field_type_settings = entityreference_field_type_settings($field); + $target_type = $field_type_settings['entity_type']; + $column = $field_type_settings['column']; $target_ids = array(); // Collect every possible entity attached to any of the entities. foreach ($entities as $id => $entity) { foreach ($items[$id] as $delta => $item) { - if (isset($item['target_id'])) { - $target_ids[] = $item['target_id']; + if (isset($item[$column])) { + $target_ids[] = $item[$column]; } } } if ($target_ids) { - $target_entities = entity_load($field['settings']['target_type'], $target_ids); + $target_entities = entity_load($target_type, $target_ids); } else { $target_entities = array(); @@ -1173,11 +1306,13 @@ function entityreference_field_formatter_prepare_view($entity_type, $entities, $ foreach ($items[$id] as $delta => $item) { // Check whether the referenced entity could be loaded. - if (isset($target_entities[$item['target_id']])) { + if (isset($target_entities[$item[$column]]) && isset($target_entities[$item[$column]])) { // Replace the instance value with the term data. - $items[$id][$delta]['entity'] = $target_entities[$item['target_id']]; + $items[$id][$delta]['entity'] = $target_entities[$item[$column]]; // Check whether the user has access to the referenced entity. - $items[$id][$delta]['access'] = entity_access('view', $field['settings']['target_type'], $target_entities[$item['target_id']]); + $has_view_access = (entity_access('view', $target_type, $target_entities[$item[$column]]) !== FALSE); + $has_update_access = (entity_access('update', $target_type, $target_entities[$item[$column]]) !== FALSE); + $items[$id][$delta]['access'] = ($has_view_access || $has_update_access); } // Otherwise, unset the instance value, since the entity does not exist. else { @@ -1199,52 +1334,91 @@ function entityreference_field_formatter_prepare_view($entity_type, $entities, $ function entityreference_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) { $result = array(); $settings = $display['settings']; - - // Rebuild the items list to contain only those with access. - foreach ($items as $key => $item) { - if (empty($item['access'])) { - unset($items[$key]); - } - } + $field_type_settings = entityreference_field_type_settings($field); + $target_type = $field_type_settings['entity_type']; + $column = $field_type_settings['column']; switch ($display['type']) { case 'entityreference_label': $handler = entityreference_get_selection_handler($field, $instance, $entity_type, $entity); foreach ($items as $delta => $item) { - $label = $handler->getLabel($item['entity']); - // If the link is to be displayed and the entity has a uri, display a link. - // Note the assignment ($url = ) here is intended to be an assignment. - if ($display['settings']['link'] && ($uri = entity_uri($field['settings']['target_type'], $item['entity']))) { - $result[$delta] = array('#markup' => l($label, $uri['path'], $uri['options'])); + // Skip an item that is not accessible, unless we're allowing output of + // entity labels without considering access. + if (empty($item['access']) && !$display['settings']['bypass_access']) { + continue; } - else { - $result[$delta] = array('#markup' => check_plain($label)); + + // Calling EntityReferenceHandler::getLabel() would make a repeated, + // wasteful call to entity_access(). + $label = entity_label($field['settings']['target_type'], $item['entity']); + + // Check if the settings and access allow a link to be displayed. + $display_link = $display['settings']['link'] && $item['access']; + + $uri = NULL; + + // If the link is allowed and the entity has a uri, display a link. + if ($display_link) { + $uri = entity_uri($target_type, $item['entity']); } + + $result[$delta] = array( + '#theme' => 'entityreference_label', + '#label' => $label, + '#item' => $item, + '#uri' => $uri, + '#settings' => array( + 'display' => $display['settings'], + 'field' => $field['settings'], + ), + ); } break; case 'entityreference_entity_id': foreach ($items as $delta => $item) { - $result[$delta] = array('#markup' => check_plain($item['target_id'])); + // Skip an item that is not accessible. + if (empty($item['access'])) { + continue; + } + + $result[$delta] = array( + '#theme' => 'entityreference_entity_id', + '#item' => $item, + '#settings' => array( + 'display' => $display['settings'], + 'field' => $field['settings'], + ), + ); } break; case 'entityreference_entity_view': + $target_langcode = $langcode; + if (!empty($settings['use_content_language']) && !empty($GLOBALS['language_content']->language)) { + $target_langcode = $GLOBALS['language_content']->language; + } + foreach ($items as $delta => $item) { + // Skip an item that is not accessible. + if (empty($item['access'])) { + continue; + } + // Protect ourselves from recursive rendering. static $depth = 0; $depth++; if ($depth > 20) { - throw new EntityReferenceRecursiveRenderingException(t('Recursive rendering detected when rendering entity @entity_type(@entity_id). Aborting rendering.', array('@entity_type' => $entity_type, '@entity_id' => $item['target_id']))); + throw new EntityReferenceRecursiveRenderingException(t('Recursive rendering detected when rendering entity @entity_type(@entity_id). Aborting rendering.', array('@entity_type' => $target_type, '@entity_id' => $item[$column]))); } - $entity = clone $item['entity']; - unset($entity->content); - $result[$delta] = entity_view($field['settings']['target_type'], array($item['target_id'] => $entity), $settings['view_mode'], $langcode, FALSE); + $target_entity = clone $item['entity']; + unset($target_entity->content); + $result[$delta] = entity_view($target_type, array($item[$column] => $target_entity), $settings['view_mode'], $target_langcode, FALSE); - if (empty($settings['links']) && isset($result[$delta][$field['settings']['target_type']][$item['target_id']]['links'])) { - $result[$delta][$field['settings']['target_type']][$item['target_id']]['links']['#access'] = FALSE; + if (empty($settings['links']) && isset($result[$delta][$target_type][$column]['links'])) { + $result[$delta][$target_type][$item[$column]]['links']['#access'] = FALSE; } $depth = 0; } @@ -1268,3 +1442,44 @@ function entityreference_views_api() { 'path' => drupal_get_path('module', 'entityreference') . '/views', ); } + +/** + * Theme label. + * + * @ingroup themeable. + */ +function theme_entityreference_label($vars) { + $label = $vars['label']; + $settings = $vars['settings']; + $item = $vars['item']; + $uri = $vars['uri']; + + $output = ''; + + // If the link is to be displayed and the entity has a uri, display a link. + // Note the assignment ($url = ) here is intended to be an assignment. + if ($settings['display']['link'] && isset($uri['path'])) { + $output .= l($label, $uri['path'], $uri['options']); + } + else { + $output .= check_plain($label); + } + + return $output; +} + +/** + * Theme entity_id + * + * @ingroup themeable. + */ +function theme_entityreference_entity_id($vars) { + $settings = $vars['settings']; + $item = $vars['item']; + + $output = ''; + + $output = check_plain($item['target_id']); + + return $output; +} diff --git a/sites/all/modules/contrib/fields/entityreference/examples/entityreference_behavior_example/entityreference_behavior_example.info b/sites/all/modules/contrib/fields/entityreference/examples/entityreference_behavior_example/entityreference_behavior_example.info index 813eab3..70bcc4f 100644 --- a/sites/all/modules/contrib/fields/entityreference/examples/entityreference_behavior_example/entityreference_behavior_example.info +++ b/sites/all/modules/contrib/fields/entityreference/examples/entityreference_behavior_example/entityreference_behavior_example.info @@ -4,9 +4,9 @@ core = 7.x package = Fields dependencies[] = entityreference -; Information added by drupal.org packaging script on 2012-11-18 -version = "7.x-1.0" +; Information added by Drupal.org packaging script on 2017-08-16 +version = "7.x-1.5" core = "7.x" project = "entityreference" -datestamp = "1353230808" +datestamp = "1502895850" diff --git a/sites/all/modules/contrib/fields/entityreference/plugins/behavior/EntityReferenceBehavior_TaxonomyIndex.class.php b/sites/all/modules/contrib/fields/entityreference/plugins/behavior/EntityReferenceBehavior_TaxonomyIndex.class.php index 43ac693..075b54d 100644 --- a/sites/all/modules/contrib/fields/entityreference/plugins/behavior/EntityReferenceBehavior_TaxonomyIndex.class.php +++ b/sites/all/modules/contrib/fields/entityreference/plugins/behavior/EntityReferenceBehavior_TaxonomyIndex.class.php @@ -144,18 +144,20 @@ class EntityReferenceBehavior_TaxonomyIndex extends EntityReference_BehaviorHand // already inserted in taxonomy_build_node_index(). $tid_all = array_diff($tid_all, $original_tid_all); - // Insert index entries for all the node's terms. + // Insert index entries for all the node's terms, preventing duplicates. if (!empty($tid_all)) { - $query = db_insert('taxonomy_index')->fields(array('nid', 'tid', 'sticky', 'created')); foreach ($tid_all as $tid) { - $query->values(array( + $row = array( 'nid' => $node->nid, 'tid' => $tid, 'sticky' => $sticky, 'created' => $node->created, - )); + ); + $query = db_merge('taxonomy_index') + ->key($row) + ->fields($row); + $query->execute(); } - $query->execute(); } } } diff --git a/sites/all/modules/contrib/fields/entityreference/plugins/selection/EntityReference_SelectionHandler_Generic.class.php b/sites/all/modules/contrib/fields/entityreference/plugins/selection/EntityReference_SelectionHandler_Generic.class.php index 2d1c5d7..6ec28a4 100644 --- a/sites/all/modules/contrib/fields/entityreference/plugins/selection/EntityReference_SelectionHandler_Generic.class.php +++ b/sites/all/modules/contrib/fields/entityreference/plugins/selection/EntityReference_SelectionHandler_Generic.class.php @@ -208,7 +208,11 @@ class EntityReference_SelectionHandler_Generic implements EntityReference_Select * Implements EntityReferenceHandler::validateAutocompleteInput(). */ public function validateAutocompleteInput($input, &$element, &$form_state, $form) { - $entities = $this->getReferencableEntities($input, '=', 6); + $bundled_entities = $this->getReferencableEntities($input, '=', 6); + $entities = array(); + foreach($bundled_entities as $entities_list) { + $entities += $entities_list; + } if (empty($entities)) { // Error if there are no entities available for a required field. form_error($element, t('There are no entities matching "%value"', array('%value' => $input))); @@ -304,7 +308,8 @@ class EntityReference_SelectionHandler_Generic implements EntityReference_Select * Implements EntityReferenceHandler::getLabel(). */ public function getLabel($entity) { - return entity_label($this->field['settings']['target_type'], $entity); + $target_type = $this->field['settings']['target_type']; + return entity_access('view', $target_type, $entity) ? entity_label($target_type, $entity) : t(ENTITYREFERENCE_DENIED); } /** @@ -338,9 +343,11 @@ class EntityReference_SelectionHandler_Generic implements EntityReference_Select // Join the known base-table. $target_type = $this->field['settings']['target_type']; $entity_info = entity_get_info($target_type); + $target_type_base_table = $entity_info['base table']; $id = $entity_info['entity keys']['id']; + // Return the alias of the table. - return $query->innerJoin($target_type, NULL, "$target_type.$id = $alias.entity_id"); + return $query->innerJoin($target_type_base_table, NULL, "%alias.$id = $alias.entity_id"); } } @@ -540,9 +547,9 @@ class EntityReference_SelectionHandler_Generic_taxonomy_term extends EntityRefer foreach ($bundles as $bundle) { if ($vocabulary = taxonomy_vocabulary_machine_name_load($bundle)) { - if ($terms = taxonomy_get_tree($vocabulary->vid, 0)) { + if ($terms = taxonomy_get_tree($vocabulary->vid, 0, NULL, TRUE)) { foreach ($terms as $term) { - $options[$vocabulary->machine_name][$term->tid] = str_repeat('-', $term->depth) . check_plain($term->name); + $options[$vocabulary->machine_name][$term->tid] = str_repeat('-', $term->depth) . check_plain(entity_label('taxonomy_term', $term)); } } } diff --git a/sites/all/modules/contrib/fields/entityreference/plugins/selection/EntityReference_SelectionHandler_Views.class.php b/sites/all/modules/contrib/fields/entityreference/plugins/selection/EntityReference_SelectionHandler_Views.class.php index 1b036a7..cbed33b 100644 --- a/sites/all/modules/contrib/fields/entityreference/plugins/selection/EntityReference_SelectionHandler_Views.class.php +++ b/sites/all/modules/contrib/fields/entityreference/plugins/selection/EntityReference_SelectionHandler_Views.class.php @@ -9,12 +9,13 @@ class EntityReference_SelectionHandler_Views implements EntityReference_Selectio * Implements EntityReferenceHandler::getInstance(). */ public static function getInstance($field, $instance = NULL, $entity_type = NULL, $entity = NULL) { - return new EntityReference_SelectionHandler_Views($field, $instance); + return new EntityReference_SelectionHandler_Views($field, $instance, $entity); } - protected function __construct($field, $instance) { + protected function __construct($field, $instance, $entity) { $this->field = $field; $this->instance = $instance; + $this->entity = $entity; } /** @@ -52,13 +53,32 @@ class EntityReference_SelectionHandler_Views implements EntityReference_Selectio ); $default = !empty($view_settings['args']) ? implode(', ', $view_settings['args']) : ''; + $description = t('Provide a comma separated list of arguments to pass to the view.') . '
    ' . t('This field supports tokens.'); + + if (!module_exists('token')) { + $description .= '
    ' . t('Install the token module to get more tokens and display available once.', array('@url' => 'http://drupal.org/project/token')); + } + $form['view']['args'] = array( '#type' => 'textfield', '#title' => t('View arguments'), '#default_value' => $default, '#required' => FALSE, - '#description' => t('Provide a comma separated list of arguments to pass to the view.'), + '#description' => $description, + '#maxlength' => '512', ); + if (module_exists('token')) { + // Get the token type for the entity type our field is in (a type 'taxonomy_term' has a 'term' type token). + $info = entity_get_info($instance['entity_type']); + + $form['view']['tokens'] = array( + '#theme' => 'token_tree', + '#token_types' => array($info['token type']), + '#global_types' => TRUE, + '#click_insert' => TRUE, + '#dialog' => TRUE, + ); + } } else { $form['view']['no_view_help'] = array( @@ -84,6 +104,7 @@ class EntityReference_SelectionHandler_Views implements EntityReference_Selectio return FALSE; } $this->view->set_display($display_name); + $this->view->pre_execute(); // Make sure the query is not cached. $this->view->is_cacheable = FALSE; @@ -104,7 +125,7 @@ class EntityReference_SelectionHandler_Views implements EntityReference_Selectio */ public function getReferencableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0) { $display_name = $this->field['settings']['handler_settings']['view']['display_name']; - $args = $this->field['settings']['handler_settings']['view']['args']; + $args = $this->handleArgs($this->field['settings']['handler_settings']['view']['args']); $result = array(); if ($this->initializeView($match, $match_operator, $limit)) { // Get the results. @@ -133,12 +154,14 @@ class EntityReference_SelectionHandler_Views implements EntityReference_Selectio function validateReferencableEntities(array $ids) { $display_name = $this->field['settings']['handler_settings']['view']['display_name']; - $args = $this->field['settings']['handler_settings']['view']['args']; + $args = $this->handleArgs($this->field['settings']['handler_settings']['view']['args']); $result = array(); if ($this->initializeView(NULL, 'CONTAINS', 0, $ids)) { // Get the results. $entities = $this->view->execute_display($display_name, $args); - $result = array_keys($entities); + if (!empty($entities)) { + $result = array_keys($entities); + } } return $result; } @@ -164,6 +187,49 @@ class EntityReference_SelectionHandler_Views implements EntityReference_Selectio } + /** + * Handles arguments for views. + * + * Replaces tokens using token_replace(). + * + * @param array $args + * Usually $this->field['settings']['handler_settings']['view']['args']. + * + * @return array + * The arguments to be send to the View. + */ + protected function handleArgs($args) { + if (!module_exists('token')) { + return $args; + } + + // Parameters for token_replace(). + $data = array(); + $options = array('clear' => TRUE); + + if ($entity = $this->entity) { + // D7 HACK: For new entities, entity and revision id are not set. This leads to + // * token replacement emitting PHP warnings + // * views choking on empty arguments + // We workaround this by filling in '0' for these IDs + // and use a clone to leave no traces of our unholy doings. + $info = entity_get_info($this->instance['entity_type']); + if (!isset($entity->{$info['entity keys']['id']})) { + $entity = clone $entity; + $entity->{$info['entity keys']['id']} = '0'; + if (!empty($info['entity keys']['revision'])) { + $entity->{$info['entity keys']['revision']} = '0'; + } + } + + $data[$info['token type']] = $entity; + } + // Replace tokens for each argument. + foreach ($args as $key => $arg) { + $args[$key] = token_replace($arg, $data, $options); + } + return $args; + } } function entityreference_view_settings_validate($element, &$form_state, $form) { diff --git a/sites/all/modules/contrib/fields/entityreference/plugins/selection/abstract.inc b/sites/all/modules/contrib/fields/entityreference/plugins/selection/abstract.inc index 1d2ea0d..a4805b1 100644 --- a/sites/all/modules/contrib/fields/entityreference/plugins/selection/abstract.inc +++ b/sites/all/modules/contrib/fields/entityreference/plugins/selection/abstract.inc @@ -21,8 +21,9 @@ interface EntityReference_SelectionHandler { * Return a list of referencable entities. * * @return - * An array of referencable entities, which keys are entity ids and - * values (safe HTML) labels to be displayed to the user. + * A nested array of entities, the first level is keyed by the + * entity bundle, which contains an array of entity labels (safe HTML), + * keyed by the entity ID. */ public function getReferencableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0); diff --git a/sites/all/modules/contrib/fields/entityreference/tests/entityreference.admin.test b/sites/all/modules/contrib/fields/entityreference/tests/entityreference.admin.test index 9a12119..ec78e7b 100644 --- a/sites/all/modules/contrib/fields/entityreference/tests/entityreference.admin.test +++ b/sites/all/modules/contrib/fields/entityreference/tests/entityreference.admin.test @@ -21,7 +21,7 @@ class EntityReferenceAdminTestCase extends DrupalWebTestCase { parent::setUp(array('field_ui', 'entity', 'ctools', 'entityreference')); // Create test user. - $this->admin_user = $this->drupalCreateUser(array('access content', 'administer content types')); + $this->admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer fields')); $this->drupalLogin($this->admin_user); // Create content type, with underscores. diff --git a/sites/all/modules/contrib/fields/entityreference/tests/entityreference.feeds.test b/sites/all/modules/contrib/fields/entityreference/tests/entityreference.feeds.test new file mode 100644 index 0000000..32248cc --- /dev/null +++ b/sites/all/modules/contrib/fields/entityreference/tests/entityreference.feeds.test @@ -0,0 +1,241 @@ + 'Feeds integration (field mapper)', + 'description' => 'Test Feeds Mapper support for fields.', + 'group' => 'Entity Reference', + 'dependencies' => array('feeds'), + ); + } + + /** + * Set-up function. + */ + public function setUp() { + parent::setUp(); + module_enable(array('entityreference_feeds_test'), TRUE); + $this->resetAll(); + + $permissions[] = 'access content'; + $permissions[] = 'administer site configuration'; + $permissions[] = 'administer content types'; + $permissions[] = 'administer nodes'; + $permissions[] = 'bypass node access'; + $permissions[] = 'administer taxonomy'; + $permissions[] = 'administer users'; + $permissions[] = 'administer feeds'; + + // Create an admin user and log in. + $this->admin_user = $this->drupalCreateUser($permissions); + $this->drupalLogin($this->admin_user); + } + + /** + * Check if mapping exists. + * + * @param string $id + * ID of the importer. + * @param integer $i + * The key of the mapping. + * @param string $source + * The source field. + * @param string $target + * The target field. + * + * @return integer + * -1 if the mapping doesn't exist, the key of the mapping otherwise. + */ + public function mappingExists($id, $i, $source, $target) { + + $current_mappings = $this->getCurrentMappings($id); + + if ($current_mappings) { + foreach ($current_mappings as $key => $mapping) { + if ($mapping['source'] == $source && $mapping['target'] == $target && $key == $i) { + return $key; + } + } + } + + return -1; + } + + /** + * Adds mappings to a given configuration. + * + * @param string $id + * ID of the importer. + * @param array $mappings + * An array of mapping arrays. Each mapping array must have a source and + * an target key and can have a unique key. + * @param bool $test_mappings + * (optional) TRUE to automatically test mapping configs. Defaults to TRUE. + */ + public function addMappings($id, $mappings, $test_mappings = TRUE) { + + $path = "admin/structure/feeds/$id/mapping"; + + // Iterate through all mappings and add the mapping via the form. + foreach ($mappings as $i => $mapping) { + + if ($test_mappings) { + $current_mapping_key = $this->mappingExists($id, $i, $mapping['source'], $mapping['target']); + $this->assertEqual($current_mapping_key, -1, 'Mapping does not exist before addition.'); + } + + // Get unique flag and unset it. Otherwise, drupalPost will complain that + // Split up config and mapping. + $config = $mapping; + unset($config['source'], $config['target']); + $mapping = array('source' => $mapping['source'], 'target' => $mapping['target']); + + // Add mapping. + $this->drupalPost($path, $mapping, t('Add')); + + // If there are other configuration options, set them. + if ($config) { + $this->drupalPostAJAX(NULL, array(), 'mapping_settings_edit_' . $i); + + // Set some settings. + $edit = array(); + foreach ($config as $key => $value) { + $edit["config[$i][settings][$key]"] = $value; + } + $this->drupalPostAJAX(NULL, $edit, 'mapping_settings_update_' . $i); + $this->drupalPost(NULL, array(), t('Save')); + } + + if ($test_mappings) { + $current_mapping_key = $this->mappingExists($id, $i, $mapping['source'], $mapping['target']); + $this->assertTrue($current_mapping_key >= 0, 'Mapping exists after addition.'); + } + } + } + + /** + * Gets an array of current mappings from the feeds_importer config. + * + * @param string $id + * ID of the importer. + * + * @return bool|array + * FALSE if the importer has no mappings, or an an array of mappings. + */ + public function getCurrentMappings($id) { + $config = db_query("SELECT config FROM {feeds_importer} WHERE id = :id", array(':id' => $id))->fetchField(); + + $config = unserialize($config); + + // We are very specific here. 'mappings' can either be an array or not + // exist. + if (array_key_exists('mappings', $config['processor']['config'])) { + $this->assertTrue(is_array($config['processor']['config']['mappings']), 'Mappings is an array.'); + + return $config['processor']['config']['mappings']; + } + + return FALSE; + } + + /** + * Basic test loading a double entry CSV file. + */ + public function test() { + $this->drupalLogin($this->admin_user); + $this->drupalGet('admin/structure/types/manage/article/fields'); + $this->assertText('Ref - entity ID', t('Found Entity reference field %field.', array('%field' => 'field_er_id'))); + $this->assertText('Ref - entity label', t('Found Entity reference field %field.', array('%field' => 'field_er_label'))); + $this->assertText('Ref - feeds GUID', t('Found Entity reference field %field.', array('%field' => 'field_er_guid'))); + $this->assertText('Ref - feeds URL', t('Found Entity reference field %field.', array('%field' => 'field_er_url'))); + + // Add feeds importer + $this->drupalGet('admin/structure/feeds'); + $this->clickLink('Add importer'); + $this->drupalPost('admin/structure/feeds/create', array('name' => 'Nodes', 'id' => 'nodes'), 'Create'); + $this->assertText('Your configuration has been created with default settings.'); + + $this->drupalPost('admin/structure/feeds/nodes/settings/', array('content_type' => '', 'import_period' => -1), 'Save'); + $this->assertText('Your changes have been saved.'); + + $this->drupalPost("admin/structure/feeds/nodes/fetcher", array('plugin_key' => 'FeedsFileFetcher'), 'Save'); + $config = unserialize(db_query("SELECT config FROM {feeds_importer} WHERE id = :id", array(':id' => 'nodes'))->fetchField()); + $this->assertEqual($config['fetcher']['plugin_key'], 'FeedsFileFetcher', 'Verified correct fetcher (FeedsFileFetcher).'); + + $this->drupalPost("admin/structure/feeds/nodes/parser", array('plugin_key' => 'FeedsCSVParser'), 'Save'); + $config = unserialize(db_query("SELECT config FROM {feeds_importer} WHERE id = :id", array(':id' => 'nodes'))->fetchField()); + $this->assertEqual($config['parser']['plugin_key'], 'FeedsCSVParser', 'Verified correct parser (FeedsCSVParser).'); + + $this->drupalPost('admin/structure/feeds/nodes/settings/FeedsNodeProcessor', array('content_type' => 'article'), 'Save'); + $this->assertText('Your changes have been saved.'); + + $this->addMappings('nodes', array( + 0 => array( + 'source' => 'title', + 'target' => 'title', + ), + 1 => array( + 'source' => 'nid', + 'target' => 'nid', + 'unique' => TRUE, + ), + 2 => array( + 'source' => 'permalink', + 'target' => 'url', + 'unique' => TRUE, + ), + 3 => array( + 'source' => 'nid', + 'target' => 'guid', + 'unique' => TRUE, + ), + 4 => array( + 'source' => 'parent_nid', + 'target' => 'field_er_id:etid', + ), + 5 => array( + 'source' => 'parent_title', + 'target' => 'field_er_label:label', + ), + 6 => array( + 'source' => 'parent_url', + 'target' => 'field_er_url:url', + ), + 7 => array( + 'source' => 'parent_guid', + 'target' => 'field_er_guid', + ), + ) + ); + + $file = realpath(getcwd()) . '/' . drupal_get_path('module', 'entityreference') . '/tests/feeds_test.csv'; + $this->assertTrue(file_exists($file), 'Source file exists'); + + $this->drupalPost('import/nodes', array('files[feeds]' => $file), 'Import'); + $this->assertText('Created 2 nodes'); + + $parent = node_load(1); + $this->assertTrue(empty($parent->field_er_id['und'][0]['target_id']), t('Parent node: Import by entity ID OK.')); + $this->assertTrue(empty($parent->field_er_label['und'][0]['target_id']), t('Parent node: Import by entity label OK.')); + $this->assertTrue(empty($parent->field_er_guid['und'][0]['target_id']), t('Parent node: Import by feeds GUID OK.')); + $this->assertTrue(empty($parent->field_er_url['und'][0]['target_id']), t('Parent node: Import by feeds URL OK.')); + + $child = node_load(2); + $this->assertTrue($child->field_er_id['und'][0]['target_id'] == 1, t('Child node: Import by entity ID OK.')); + $this->assertTrue($child->field_er_label['und'][0]['target_id'] == 1, t('Child node: Import by entity label OK.')); + $this->assertTrue($child->field_er_guid['und'][0]['target_id'] == 1, t('Child node: Import by feeds GUID OK.')); + $this->assertTrue($child->field_er_url['und'][0]['target_id'] == 1, t('Child node: Import by feeds URL OK.')); + } +} diff --git a/sites/all/modules/contrib/fields/entityreference/tests/entityreference.handlers.test b/sites/all/modules/contrib/fields/entityreference/tests/entityreference.handlers.test index 03bc54f..1367b44 100644 --- a/sites/all/modules/contrib/fields/entityreference/tests/entityreference.handlers.test +++ b/sites/all/modules/contrib/fields/entityreference/tests/entityreference.handlers.test @@ -84,6 +84,14 @@ class EntityReferenceHandlersTestCase extends DrupalWebTestCase { 'title' => 'Node unpublished (<&>)', 'uid' => 1, ), + // Title purposefully starts with same characters as published1 and + // published2 above but contains a slash. + 'published_withslash' => (object) array( + 'type' => 'article', + 'status' => 1, + 'title' => 'Node pub/lished1', + 'uid' => 1, + ), ); $node_labels = array(); @@ -104,6 +112,7 @@ class EntityReferenceHandlersTestCase extends DrupalWebTestCase { 'article' => array( $nodes['published1']->nid => $node_labels['published1'], $nodes['published2']->nid => $node_labels['published2'], + $nodes['published_withslash']->nid => $node_labels['published_withslash'], ), ), ), @@ -141,6 +150,18 @@ class EntityReferenceHandlersTestCase extends DrupalWebTestCase { ), 'result' => array(), ), + // Searching for "Node pub/" should return only the published_withslash node + // and not published1 and published2 from above. + array( + 'arguments' => array( + array('Node pub/', 'CONTAINS'), + ), + 'result' => array( + 'article' => array( + $nodes['published_withslash']->nid => $node_labels['published_withslash'], + ), + ), + ), ); $this->assertReferencable($field, $referencable_tests, 'Node handler'); @@ -156,6 +177,7 @@ class EntityReferenceHandlersTestCase extends DrupalWebTestCase { 'article' => array( $nodes['published1']->nid => $node_labels['published1'], $nodes['published2']->nid => $node_labels['published2'], + $nodes['published_withslash']->nid => $node_labels['published_withslash'], $nodes['unpublished']->nid => $node_labels['unpublished'], ), ), @@ -172,6 +194,21 @@ class EntityReferenceHandlersTestCase extends DrupalWebTestCase { ), ); $this->assertReferencable($field, $referencable_tests, 'Node handler (admin)'); + + // Verify autocomplete input validation. + $handler = entityreference_get_selection_handler($field); + $element = array( + '#parents' => array('element_name'), + ); + $form_state = array(); + $form = array(); + $value = $handler->validateAutocompleteInput($nodes['published1']->title, $element, $form_state, $form); + $this->assertEqual($value, $nodes['published1']->nid); + + $invalid_input = $this->randomName(); + $value = $handler->validateAutocompleteInput($invalid_input, $element, $form_state, $form); + $this->assertNull($value); + $this->assertEqual(form_get_error($element), t('There are no entities matching "%value"', array('%value' => $invalid_input))); } /** @@ -234,7 +271,7 @@ class EntityReferenceHandlersTestCase extends DrupalWebTestCase { ), 'result' => array( 'user' => array( - $users['admin']->uid => $user_labels['admin'], + $users['admin']->uid => ENTITYREFERENCE_DENIED, $users['non_admin']->uid => $user_labels['non_admin'], ), ), diff --git a/sites/all/modules/contrib/fields/entityreference/tests/entityreference.taxonomy.test b/sites/all/modules/contrib/fields/entityreference/tests/entityreference.taxonomy.test index 6e4afb7..94b3f56 100644 --- a/sites/all/modules/contrib/fields/entityreference/tests/entityreference.taxonomy.test +++ b/sites/all/modules/contrib/fields/entityreference/tests/entityreference.taxonomy.test @@ -112,4 +112,52 @@ class EntityReferenceTaxonomyTestCase extends DrupalWebTestCase { $this->assertFalse(taxonomy_select_nodes(1)); } + /** + * Add a second ER field from node/article to taxonomy. + * + * This should not cause {taxonomy_index} to receive duplicate entries. + */ + protected function setupForIndexDuplicates() { + // Create an entity reference field. + $field = array( + 'entity_types' => array('node'), + 'settings' => array( + 'handler' => 'base', + 'target_type' => 'taxonomy_term', + 'handler_settings' => array( + 'target_bundles' => array(), + ), + ), + 'field_name' => 'field_entityreference_term2', + 'type' => 'entityreference', + ); + $field = field_create_field($field); + $instance = array( + 'field_name' => 'field_entityreference_term2', + 'bundle' => 'article', + 'entity_type' => 'node', + ); + + // Enable the taxonomy-index behavior. + $instance['settings']['behaviors']['taxonomy-index']['status'] = TRUE; + field_create_instance($instance); + } + + /** + * Make sure the index only contains one entry for a given node->term + * reference, even when multiple ER fields link from the node bundle to terms. + */ + public function testIndexDuplicates() { + // Extra setup for this test: add another ER field on this content type. + $this->setupForIndexDuplicates(); + + // Assert node insert with reference to term in first field. + $tid = 1; + $settings = array(); + $settings['type'] = 'article'; + $settings['field_entityreference_term'][LANGUAGE_NONE][0]['target_id'] = $tid; + $node = $this->drupalCreateNode($settings); + + $this->assertEqual(taxonomy_select_nodes($tid), array($node->nid)); + } } diff --git a/sites/all/modules/contrib/fields/entityreference/tests/feeds_test.csv b/sites/all/modules/contrib/fields/entityreference/tests/feeds_test.csv new file mode 100644 index 0000000..c145ce3 --- /dev/null +++ b/sites/all/modules/contrib/fields/entityreference/tests/feeds_test.csv @@ -0,0 +1,3 @@ +"nid","title","permalink","parent_nid","parent_title","parent_url","parent_guid" +"1","Views","http://drupal.org/project/views","","","","" +"2","Views bulk operations","http://drupal.org/project/views_bulk_operations","1","Views","http://drupal.org/project/views","1" diff --git a/sites/all/modules/contrib/fields/entityreference/tests/modules/entityreference_feeds_test/entityreference_feeds_test.info b/sites/all/modules/contrib/fields/entityreference/tests/modules/entityreference_feeds_test/entityreference_feeds_test.info new file mode 100644 index 0000000..5bbf9b6 --- /dev/null +++ b/sites/all/modules/contrib/fields/entityreference/tests/modules/entityreference_feeds_test/entityreference_feeds_test.info @@ -0,0 +1,16 @@ +name = "Entityreference - Feeds integration tests" +description = "Support module for the Entityreference - Feeds integration tests." +package = Testing +core = 7.x +hidden = TRUE + +dependencies[] = feeds +dependencies[] = feeds_ui +dependencies[] = entityreference + +; Information added by Drupal.org packaging script on 2017-08-16 +version = "7.x-1.5" +core = "7.x" +project = "entityreference" +datestamp = "1502895850" + diff --git a/sites/all/modules/contrib/fields/entityreference/tests/modules/entityreference_feeds_test/entityreference_feeds_test.module b/sites/all/modules/contrib/fields/entityreference/tests/modules/entityreference_feeds_test/entityreference_feeds_test.module new file mode 100644 index 0000000..f16b360 --- /dev/null +++ b/sites/all/modules/contrib/fields/entityreference/tests/modules/entityreference_feeds_test/entityreference_feeds_test.module @@ -0,0 +1,104 @@ + 'field_er_id', + 'type' => 'entityreference', + 'cardinality' => 1, + 'settings' => array( + 'target_type' => 'node', + 'handler' => 'base', + 'handler_settings' => array( + 'target_bundles' => array('article' => 'article'), + ), + ), + ); + field_create_field($field); + + $instance = array( + 'field_name' => 'field_er_id', + 'entity_type' => 'node', + 'label' => 'Ref - entity ID', + 'bundle' => 'article', + ); + field_create_instance($instance); + + // Entity reference field - mapped by Feeds GUID. + $field = array( + 'field_name' => 'field_er_guid', + 'type' => 'entityreference', + 'cardinality' => 1, + 'settings' => array( + 'target_type' => 'node', + 'handler' => 'base', + 'handler_settings' => array( + 'target_bundles' => array('article' => 'article'), + ), + ), + ); + field_create_field($field); + + $instance = array( + 'field_name' => 'field_er_guid', + 'entity_type' => 'node', + 'label' => 'Ref - feeds GUID', + 'bundle' => 'article', + ); + field_create_instance($instance); + + // Entity reference field - mapped by Feeds URL. + $field = array( + 'field_name' => 'field_er_url', + 'type' => 'entityreference', + 'cardinality' => 1, + 'settings' => array( + 'target_type' => 'node', + 'handler' => 'base', + 'handler_settings' => array( + 'target_bundles' => array('article' => 'article'), + ), + ), + ); + field_create_field($field); + + $instance = array( + 'field_name' => 'field_er_url', + 'entity_type' => 'node', + 'label' => 'Ref - feeds URL', + 'bundle' => 'article', + ); + field_create_instance($instance); + + // Entity reference field - mapped by Label. + $field = array( + 'field_name' => 'field_er_label', + 'type' => 'entityreference', + 'cardinality' => 1, + 'settings' => array( + 'target_type' => 'node', + 'handler' => 'base', + 'handler_settings' => array( + 'target_bundles' => array('article' => 'article'), + ), + ), + ); + field_create_field($field); + + $instance = array( + 'field_name' => 'field_er_label', + 'entity_type' => 'node', + 'label' => 'Ref - entity label', + 'bundle' => 'article', + ); + field_create_instance($instance); +} diff --git a/sites/all/modules/contrib/fields/entityreference/views/entityreference.views.inc b/sites/all/modules/contrib/fields/entityreference/views/entityreference.views.inc index df38395..baa7034 100644 --- a/sites/all/modules/contrib/fields/entityreference/views/entityreference.views.inc +++ b/sites/all/modules/contrib/fields/entityreference/views/entityreference.views.inc @@ -80,6 +80,17 @@ function entityreference_field_views_data_views_data_alter(&$data, $field) { 'group' => t('Entity Reference'), 'title' => t('Referencing entity'), 'help' => t('A bridge to the @entity entity that is referencing @target_entity via !field_name', $replacements), + 'join_extra' => array( + 0 => array( + 'field' => 'entity_type', + 'value' => $entity_type, + ), + 1 => array( + 'field' => 'deleted', + 'value' => 0, + 'numeric' => TRUE, + ), + ), ); } } diff --git a/sites/all/modules/contrib/fields/entityreference/views/entityreference_plugin_display.inc b/sites/all/modules/contrib/fields/entityreference/views/entityreference_plugin_display.inc index 7a1b768..cca3650 100644 --- a/sites/all/modules/contrib/fields/entityreference/views/entityreference_plugin_display.inc +++ b/sites/all/modules/contrib/fields/entityreference/views/entityreference_plugin_display.inc @@ -75,8 +75,16 @@ class entityreference_plugin_display extends views_plugin_display { // Build the condition using the selected search fields foreach ($style_options['search_fields'] as $field_alias) { if (!empty($field_alias)) { - // Get the table and field names for the checked field - $field = $this->view->query->fields[$this->view->field[$field_alias]->field_alias]; + + // Get the table and field names for the checked field. + if (empty($this->view->field[$field_alias]->field_info)) { + $field = $this->view->query->fields[$this->view->field[$field_alias]->field_alias]; + } + else { + $field_table = $this->view->query->ensure_table($this->view->field[$field_alias]->table, $this->view->field[$field_alias]->relationship); + $this->view->query->add_field($field_table, $this->view->field[$field_alias]->real_field, $this->view->field[$field_alias]->field, array()); + $field = $this->view->query->fields[$this->view->field[$field_alias]->field]; + } // Add an OR condition for the field $conditions->condition($field['table'] . '.' . $field['field'], $value, 'LIKE'); } diff --git a/sites/all/modules/contrib/fields/entityreference/views/entityreference_plugin_style.inc b/sites/all/modules/contrib/fields/entityreference/views/entityreference_plugin_style.inc index 76c2e81..b72a235 100644 --- a/sites/all/modules/contrib/fields/entityreference/views/entityreference_plugin_style.inc +++ b/sites/all/modules/contrib/fields/entityreference/views/entityreference_plugin_style.inc @@ -26,7 +26,7 @@ class entityreference_plugin_style extends views_plugin_style { '#title' => t('Search fields'), '#options' => $options, '#required' => TRUE, - '#default_value' => $this->options['search_fields'], + '#default_value' => isset($this->options['search_fields']) ? $this->options['search_fields'] : array(), '#description' => t('Select the field(s) that will be searched when using the autocomplete widget.'), '#weight' => -3, ); @@ -45,19 +45,17 @@ class entityreference_plugin_style extends views_plugin_style { // Group the rows according to the grouping field, if specified. $sets = $this->render_grouping($this->view->result, $this->options['grouping']); - // Grab the alias of the 'id' field added by entityreference_plugin_display. $id_field_alias = $this->display->handler->id_field_alias; // @todo We don't display grouping info for now. Could be useful for select // widget, though. $results = array(); - $this->view->row_index = 0; foreach ($sets as $records) { - foreach ($records as $values) { + foreach ($records as $index => $values) { + $this->view->row_index = $index; // Sanitize html, remove line breaks and extra whitespace. $results[$values->{$id_field_alias}] = filter_xss_admin(preg_replace('/\s\s+/', ' ', str_replace("\n", '', $this->row_plugin->render($values)))); - $this->view->row_index++; } } unset($this->view->row_index); diff --git a/sites/all/modules/contrib/fields/field_group/CHANGELOG.txt b/sites/all/modules/contrib/fields/field_group/CHANGELOG.txt old mode 100755 new mode 100644 index b4a64bb..5798162 --- a/sites/all/modules/contrib/fields/field_group/CHANGELOG.txt +++ b/sites/all/modules/contrib/fields/field_group/CHANGELOG.txt @@ -1,12 +1,75 @@ /* $Id*/ CHANGELOG for field_group for Drupal 7 -Field_group 7.x-1.x-dev - o Issue #1095316: Field Groups disappear when Content Type is renamed. - o Issue #1095316 by swentel: Support for Entity API. - o Issue #1095002 by animelion: Upgrading removes all existing field groups. - o Issue #1095130 by willvincent: Features export not working with rc2. - +Field Group origin/7.x-1.x, 2017-11-04 +-------------------------------------- +- Tests fail after new 'administer fields' permission. +- Update CHANGELOG.txt to 7.x-1.5 Release. +- Revert "Issue #2646106 by jribeiro: Update CHANGELOG.txt to 7.x-1.5 Release". +- Update CHANGELOG.txt to 7.x-1.5 Release. +- Fix whitespace between "&" and parameters for some functions. +- Avoid bad code pattern (that is picked up by security scanners) when getting hash. +- Should #array_parents be updated in field_group_fields_nest()?. +- "Show" appears in horizontal tab title if node form submitted through ajax. +- CSS breaks i18n string translation interface. +- "uncaught exception: Syntax error, unrecognized expression: #" after 7.x-1.4 update. +- PHP7 - Uniform Variable Syntax updates are causing exported field_groups to not have names. + +Field_group 7.x-1.5 + o Added extra formatting on html element attributes. + o Fixed bug on automatic ID generation in javascript. + o Issue #2511960 by rrfegade: spelling errors in D7. + o Issue #2194279 by Georgique: Export translatables (label and descriptions) in Features. + o Issue #2386335 by Dubs: Integer Group Array Keys can create empty node forms. + o Issue #2269133 by sammuell: Accordion is always closed when using jQuery update module. + o Issue #2258939 by Snipon: Fixed Change to preventDefault on multipage controls. + o Issue #2173937 by jantoine, omerida | Bernieman: Fixed Accordion Element has fixed height in HTML. + o Issue #2311789 by eelkeblok: Fixed Group is incorrectly determined to be empty with nested form elements. + o Issue #2272003 by karolrybak | Kwb: Fixed Fieldgroup no longer renders after using more than one 'HTML Element' on the same level. + o Issue #2309219 by czigor: Fixed Proper CTools exportable loading. + o Issue #2283245 by stefan.r | jrb: Fixed New id functionality breaks tests and CSS on existing field groups. + o Issue #2295133 by boyan.borisov, interX: Fixed field_group_update_7007 error. + o Remove field_group.css, the css is not needed anymore. + o Fix horiziontal tabs when used as standalone element. + +Field_group 7.x-1.4 + o Issue #2129805 by RaF: Incorrect markup when open div & custom classes provided. + o Issue #2037731 by maximpodorov, Zach Harkey: Remove id attribute from HTML elements. + o Issue #2099505 by borisson_ | Maks: Warning: Invalid argument supplied for foreach() in field_group_build_entity_groups() (line 1894 ..sites/all/modules/field_group. + o Issue #2102397 by thebruce: Field Group 7006 update fails with "Cannot use string offset as an array in /field_group/field_group.install on line 318. + o Issue #2122733 by Alan D.: Remove test module from admin listing. + o Issue #2212431 by tim.plunkett: Field groups that contain only elements that use #markup are hidden. + o Issue #2175731 by tobiasb, daggerhart: HTML produced by module is broken. + o Issue #2190425 by tobiasb: Fix attach behavior. + o Issue #1911530 by tobiasb, Simon Georges: Remove useless files[] directive from .info files. + o Issue #1358430 by oushen, tobiasb | FriedJam: JS "Error: uncaught exception: Syntax error, unrecognized expression:". + o Issue #2104391 by mike_san: Package descriptions. + o Issue #2189777 by gmercer: Exporting a feature with field groups sometimes leads to $field_group->data being set to an empty string. + o Issue #2168689 by Paul B: Beïng should be being. + o Issue #2037731 by rreiss, maximpodorov, Zach Harkey: Make id attribute optional. Current installations will default get the old ids + o Issue #2173351 by axe312: Add a label to html-element wrappers. + o Issue #1954056 by klonos: Proper capitalization in the project name. + o Issue #2224547 by sdrycroft: Fixed field_group_info_groups() clears all CTools caches when it should only clear the field_group caches. + o Issue #2099505 by joelpittet, borisson_ | Maks: Fixed Warning: Invalid argument supplied for foreach() in field_group_build_entity_groups() (line 1894 ..sites/all/modules/field_group. + o Issue #2223269 by barraponto: Fixed Don't filter and translate empty strings. + +Field_group 7.x-1.3 + o Issue #2077695 by FreekVR | ChoY: Fixed field group entity display bug after update. + o Issue #2078201 by DeFr, eneko1907 | cmriley: Fixed Started getting a ton of notices. + o Issue #2085137 by ar-jan: Fixed Collapse speed no longer works with field_group() 1.2 + o Issue #2085225 by dtarc: Fixed Featurized Field Groups no longer have classes since marked optional. + o Issue #2080645 by bassthiam: Fixed Warning: Illegal string offset 'label' in field_group_unpack() line 1682 of field_group()/field_group().module. + o Issue #1942676 by Harlor | El Bandito: Added Change the text 'Mark group for required fields.'. + o Issue #1358430 by FriedJam: Fixed JS 'Error: uncaught exception: Syntax error, unrecognized expression:". + o Issue #2084999 by activelink: Fixed Empty field groups aren't being suppressed since move to 7.x-1.2. + o Issue #1557258 by Mark Trapp | Chris Gillis: Fixed Existing field groups disappear and Add Field Group UI is disabled when no fields are available. + o Issue #2000110 by bigjim: Fixed Comments on field_group_read_groups() wrong + o Issue #1169146 by ankur, Nephele | jeffrey.dalton: Fixed upgrade from 7.x-1.0-rc2 to 7.x-1.0 deletes all field_groups(). + o Vertical tabs always appear when children elements have no access + o #1966624 Feature 7.x-2.0-beta2 defines fields in a new way + o #2090415 Make horizontal tabs not dependent on form.js. Also change drupal_add_x to #attached + o + Field_group 7.x-1.0-rc2 o Ran through coder, minor. o Issue #1033036 by Stalski, swentel: Create a field_group.api.php. @@ -20,7 +83,7 @@ Field_group 7.x-1.0-rc2 o #1045526 by stalski: Make formatter options more user-friendly and logical. o #1041880 by robertgarrigos: duplicated entries in field_group table. o #1043834 by amsri: Field Group module just does not work with profiles 2. - + Field_group 7.x-1.0-rc1 o #1006464 Change #groups to #fieldgroups because of name collapsing with form_process_fieldset o #1024184 fix collapsible when mode is set to open @@ -34,7 +97,7 @@ Field_group 7.x-1.0-rc1 o #961106 Fixed notice on entity type and bundle check. o #962072 by mori: Improve CSS for horizontal tabs & accordion. o Changed Fieldgroup API: defaults and instance_settings are now merged. - o Changed save action so everything is gathered during form_state to + o Changed save action so everything is gathered during form_state to postpone saving until the save button is hit. o Changed some important variable name, so it makes more sense and easier to read. o Add basic crud functions. \ No newline at end of file diff --git a/sites/all/modules/contrib/fields/field_group/README.txt b/sites/all/modules/contrib/fields/field_group/README.txt old mode 100755 new mode 100644 index eb6efc2..311b99e --- a/sites/all/modules/contrib/fields/field_group/README.txt +++ b/sites/all/modules/contrib/fields/field_group/README.txt @@ -1,28 +1,38 @@ History: - Field_group was written for Drupal 7. For drupal 6, the module is + Field_group was written for Drupal 7. For drupal 6, the module is located in the CCK module (http://drupal.org/project/cck). - As drupal core has a fields API drupal > 6, the field_group module + As drupal core has a fields API drupal > 6, the field_group module is considered a contribution. - + Description: - field_group is a module that will group a set of fields. In Drupal7, + field_group is a module that will group a set of fields. In Drupal7, with fields, one means all fields that come from fieldable entities. You can add fieldgroups in several types with their own format settings. - field_group has API functions to add your own formatter and rendering for - it. + field_group has API functions to add your own formatter and rendering for + it. One of the biggest improvements to previous versions, is that fieldgroups have unlimited nesting, better display control. - Note that field_group will only group fields, it can not be used to hide + Note that field_group will only group fields, it can not be used to hide certain fields since this a permission matter. Module project page: http://drupal.org/project/field_group - -Documentation page: + +Documentation page: http://drupal.org/node/1017838 http://drupal.org/node/1017962 - + +Available group types: + - Fieldsets + - Horizontal tabs + - Vertical tabs + - Accordions + - Divs + - Multipage steps: Note: This is only client side. + - HTML5 group type + - Html element + To submit bug reports and feature suggestions, or to track changes: http://drupal.org/project/issues/field_group diff --git a/sites/all/modules/contrib/fields/field_group/field_group-rtl.css b/sites/all/modules/contrib/fields/field_group/field_group-rtl.css new file mode 100644 index 0000000..26c1816 --- /dev/null +++ b/sites/all/modules/contrib/fields/field_group/field_group-rtl.css @@ -0,0 +1,7 @@ +/** + * Override the accordion default style for view_modes. + */ +form .ui-accordion h3, form .ui-accordion h3.ui-state-active { + padding-left: 0; + padding-right: 2em; +} diff --git a/sites/all/modules/contrib/fields/field_group/field_group.api.php b/sites/all/modules/contrib/fields/field_group/field_group.api.php old mode 100755 new mode 100644 index 2ace3ee..fdcbe03 --- a/sites/all/modules/contrib/fields/field_group/field_group.api.php +++ b/sites/all/modules/contrib/fields/field_group/field_group.api.php @@ -2,7 +2,7 @@ /** * @file - * Hooks provided by the Field group module. + * Hooks provided by the Field Group module. * * Fieldgroup is a module that will wrap fields and other fieldgroups. Nothing more, nothing less. * For this there are formatters we can create on forms and view modes. @@ -87,7 +87,7 @@ function hook_field_group_formatter_info() { 'form' => array( 'fieldset' => array( 'label' => t('Fieldset'), - 'description' => t('This fieldgroup renders the inner content in a fieldset with the titel as legend.'), + 'description' => t('This fieldgroup renders the inner content in a fieldset with the title as legend.'), 'format_types' => array('open', 'collapsible', 'collapsed'), 'instance_settings' => array('classes' => ''), 'default_formatter' => 'collapsible', @@ -96,7 +96,7 @@ function hook_field_group_formatter_info() { 'display' => array( 'div' => array( 'label' => t('Div'), - 'description' => t('This fieldgroup renders the inner content in a simple div with the titel as legend.'), + 'description' => t('This fieldgroup renders the inner content in a simple div with the title as legend.'), 'format_types' => array('open', 'collapsible', 'collapsed'), 'instance_settings' => array('effect' => 'none', 'speed' => 'fast', 'classes' => ''), 'default_formatter' => 'collapsible', @@ -220,7 +220,7 @@ function hook_field_group_format_settings($group) { * @param Array $elements by address. * @param Object $group The Field group info. */ -function hook_field_group_pre_render(& $element, $group, & $form) { +function hook_field_group_pre_render(&$element, $group, &$form) { // You can prepare some variables to use in the logic. $view_mode = isset($form['#view_mode']) ? $form['#view_mode'] : 'form'; @@ -268,7 +268,7 @@ function hook_field_group_pre_render(& $element, $group, & $form) { * * Function that fungates as last resort to alter the pre_render build. */ -function hook_field_group_pre_render_alter(&$element, $group, & $form) { +function hook_field_group_pre_render_alter(&$element, $group, &$form) { if ($group->format_type == 'htab') { $element['#theme_wrappers'] = array('my_horizontal_tab'); @@ -285,7 +285,7 @@ function hook_field_group_pre_render_alter(&$element, $group, & $form) { * * @param Array $elements by address. */ -function hook_field_group_build_pre_render_alter(& $element) { +function hook_field_group_build_pre_render_alter(&$element) { // Prepare variables. $display = isset($element['#view_mode']); @@ -345,6 +345,18 @@ function hook_field_group_info() { } +/** + * Alter the field group definitions provided by other modules. + * + * @param array $groups + * Reference to an array of field group definition objects. + */ +function hook_field_group_info_alter(&$groups) { + if (!empty($groups['group_issue_metadata|node|project_issue|form'])) { + $groups['group_issue_metadata|node|project_issue|form']->data['children'][] = 'taxonomy_vocabulary_9'; + } +} + /** * Implements hook_field_group_update_field_group(). * @@ -421,14 +433,27 @@ function field_group_info_groups($entity_type = NULL, $bundle = NULL, $view_mode * * @param Array $params * The Entity type where field groups are requested. + * @param $enabled + * Return enabled or disabled groups.* * * @see field_group_info_groups() * @see ctools_export_load_object() */ -function field_group_read_groups($params = array()) { +function field_group_read_groups($conditions = array(), $enabled = TRUE) { // This function loads the requested groups through ctools export api. } +/** + * Hides field groups including children in a render array. + * + * @param array $element + * A render array. Can be a form, node, user, ... + * @param array $group_names + * An array of field group names that should be hidden. + */ +function field_group_hide_field_groups(&$element, $group_names) { +} + /** * @} End of "addtogroup utility functions". */ diff --git a/sites/all/modules/contrib/fields/field_group/field_group.css b/sites/all/modules/contrib/fields/field_group/field_group.css deleted file mode 100755 index 5117873..0000000 --- a/sites/all/modules/contrib/fields/field_group/field_group.css +++ /dev/null @@ -1,32 +0,0 @@ -/* $Id: field_group.css,v 1.1.2.12 2010/12/22 22:22:35 stalski Exp $ */ - -/** - * Fix for fieldsets in vertical tabs. - * Note that this can only be hardcoded to the Seven theme - * where people who override this, are in trouble. - */ -.vertical-tabs fieldset.default-fallback, -div.field-group-tabs-wrapper div.field-type-image fieldset, -div.field-group-tabs-wrapper div.field-type-file fieldset { - border: 1px solid #CCCCCC; - margin: 1em 0; - padding: 2.5em 0 0; - position: relative; -} - -div.field-group-tabs-wrapper div.field-type-image legend, -div.field-group-tabs-wrapper div.field-type-file legend, -.vertical-tabs .field-group-fieldset legend { - display: block; -} - -/** - * Override the accordion default style for view_modes. - */ -form .ui-accordion h3, form .ui-accordion h3.ui-state-active { - padding-left: 2em; -} - -.ui-accordion h3.ui-state-active { - background: url("/misc/ui/images/ui-bg_glass_75_e6e6e6_1x400.png") repeat-x scroll 50% 50% #E6E6E6; -} \ No newline at end of file diff --git a/sites/all/modules/contrib/fields/field_group/field_group.features.inc b/sites/all/modules/contrib/fields/field_group/field_group.features.inc old mode 100755 new mode 100644 index cea7833..a4484b5 --- a/sites/all/modules/contrib/fields/field_group/field_group.features.inc +++ b/sites/all/modules/contrib/fields/field_group/field_group.features.inc @@ -9,25 +9,42 @@ * that are a part of this feature. */ function field_group_features_export_alter(&$export, $module_name) { - //Make sure we have fresh data by loading directly. + // Make sure we have fresh data by loading directly. ctools_include('export'); $field_groups = ctools_export_load_object('field_group'); - //Add fieldgroups based on the fields that are present. - if (!empty($export['features']['field'])) { - foreach ($export['features']['field'] as $field) { + // Support the separate field base -vs- field instance structure that was + // added in Features v7.x-2.0-beta2. + if (function_exists('field_instance_features_export')) { + $export_var = 'field_instance'; + } + else { + $export_var = 'field'; + } + + // Add fieldgroups based on the fields that are present. + if (!empty($export['features'][$export_var])) { + if (!isset($export['features']['field_group'])) { + $export['features']['field_group'] = array(); + } + foreach ($export['features'][$export_var] as $field) { list($entity_type, $bundle, $field_name) = explode('-', $field); foreach ($field_groups as $group_id => $group) { - if ($group->entity_type == $entity_type && $group->bundle == $bundle - && in_array($field_name, $group->data['children'])) { - $export['features']['field_group'][$group_id] = $group_id; + + if ($group->entity_type == $entity_type && $group->bundle == $bundle && in_array($field_name, $group->data['children']) && !in_array($group->identifier, $export['features']['field_group'])) { + if (isset($group->export_module) && $group->export_module != $module_name) { + $export['dependencies'][$group->export_module] = $group->export_module; + } + else { + $export['features']['field_group'][$group_id] = $group_id; + } } } } } - //Add any parent field groups that haven't been selected. + // Add any parent field groups that haven't been selected. if (!empty($export['features']['field_group'])) { foreach ($export['features']['field_group'] as $id) { $group = isset($field_groups[$id]) ? $field_groups[$id] : FALSE; @@ -37,7 +54,12 @@ function field_group_features_export_alter(&$export, $module_name) { $parent_group = isset($field_groups[$parent_id]) ? $field_groups[$parent_id] : FALSE; if ($parent_group && !isset($export['features']['field_group'][$parent_id])) { - $export['features']['field_group'][$parent_id] = $parent_id; + if (isset($parent_group->export_module) && $parent_group->export_module != $module_name) { + $export['dependencies'][$parent_group->export_module] = $parent_group->export_module; + } + else { + $export['features']['field_group'][$parent_id] = $parent_id; + } } } } diff --git a/sites/all/modules/contrib/fields/field_group/field_group.field_ui.css b/sites/all/modules/contrib/fields/field_group/field_group.field_ui.css old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/fields/field_group/field_group.field_ui.inc b/sites/all/modules/contrib/fields/field_group/field_group.field_ui.inc old mode 100755 new mode 100644 index 47a1545..dbd26d0 --- a/sites/all/modules/contrib/fields/field_group/field_group.field_ui.inc +++ b/sites/all/modules/contrib/fields/field_group/field_group.field_ui.inc @@ -1,815 +1,1013 @@ -entity_type = $form['#entity_type']; - $params->bundle = $form['#bundle']; - $params->admin_path = _field_ui_bundle_admin_path($params->entity_type, $params->bundle); - $params->display_overview = $display_overview; - - $formatters = field_group_formatter_info(); - - if ($display_overview) { - $params->region_callback = 'field_group_display_overview_row_region'; - $params->mode = $form['#view_mode']; - } - else { - $params->region_callback = 'field_group_field_overview_row_region'; - $params->mode = 'form'; - } - - $params->groups = field_group_info_groups($params->entity_type, $params->bundle, $params->mode, TRUE); - - // Gather parenting data. - $params->parents = array(); - foreach ($params->groups as $name => $group) { - foreach ($group->children as $child) { - $params->parents[$child] = $name; - } - } - - return $params; -} - -/** - * Function to alter the fields overview and display overview screen. - */ -function field_group_field_ui_overview_form_alter(&$form, &$form_state, $display_overview = FALSE) { - - // Only start altering the form if we need to. - if (empty($form['#fields']) && empty($form['#extra'])) { - return; - } - - $params = field_group_field_ui_form_params($form, $display_overview); - - // Add some things to be able to preserve synced usage of field_ui. - if (!$display_overview) { - // This key is used to store the current updated field. - $form_state += array( - 'formatter_settings_edit' => NULL, - ); - // Add AJAX wrapper. - $form['fields']['#prefix'] = '
    '; - $form['fields']['#suffix'] = '
    '; - } - $form['#groups'] = array_keys($params->groups); - - $table = &$form['fields']; - - // Add a region for 'add_new' rows, but only when fields are - // available and thus regions. - if (isset($table['#regions'])) { - $table['#regions'] += array( - 'add_new' => array('title' => ' '), - ); - } - - // Extend available parenting options. - foreach ($params->groups as $name => $group) { - $table['#parent_options'][$name] = $group->label; - } - $table['#parent_options']['_add_new_group'] = t('Add new group'); - - // Update existing rows accordingly to the parents. - foreach (element_children($table) as $name) { - $table[$name]['parent_wrapper']['parent']['#options'] = $table['#parent_options']; - // Inherit the value of the parent when default value is empty. - if (empty($table[$name]['parent_wrapper']['parent']['#default_value'])) { - $table[$name]['parent_wrapper']['parent']['#default_value'] = isset($params->parents[$name]) ? $params->parents[$name] : ''; - } - } - - $formatter_options = field_group_field_formatter_options($display_overview ? 'display' : 'form'); - - $refresh_rows = isset($form_state['values']['refresh_rows']) ? $form_state['values']['refresh_rows'] : (isset($form_state['input']['refresh_rows']) ? $form_state['input']['refresh_rows'] : NULL); - - // Create the group rows and check actions. - foreach (array_keys($params->groups) as $name) { - - // Play around with form_state so we only need to hold things - // between requests, until the save button was hit. - if (isset($form_state['field_group'][$name])) { - $group = & $form_state['field_group'][$name]; - } - else { - $group = & $params->groups[$name]; - } - - // Check the currently selected formatter, and merge persisted values for - // formatter settings for the group. - // This needs to be done first, so all fields are updated before creating form elements. - if (isset($refresh_rows) && $refresh_rows == $name) { - $settings = isset($form_state['values']['fields'][$name]) ? $form_state['values']['fields'][$name] : (isset($form_state['input']['fields'][$name]) ? $form_state['input']['fields'][$name] : NULL); - if (array_key_exists('settings_edit', $settings)) { - //$group->format_type = $form_state['field_group'][$name]->format_type; - $group = $form_state['field_group'][$name]; - } - field_group_formatter_row_update($group, $settings); - } - - // Save the group when the configuration is submitted. - if (!empty($form_state['values'][$name . '_formatter_settings_update'])) { - field_group_formatter_settings_update($group, $form_state['values']['fields'][$name]); - } - // After all updates are finished, let the form_state know. - $form_state['field_group'][$name] = $group; - - $settings = field_group_format_settings_form($group); - - $id = strtr($name, '_', '-'); - $js_rows_data[$id] = array('type' => 'group', 'name' => $name); - // A group cannot be selected as its own parent. - $parent_options = $table['#parent_options']; - unset($parent_options[$name]); - $table[$name] = array( - '#attributes' => array('class' => array('draggable', 'field-group'), 'id' => $id), - '#row_type' => 'group', - '#region_callback' => $params->region_callback, - '#js_settings' => array('rowHandler' => 'group'), - 'human_name' => array( - '#markup' => check_plain(t($group->label)), - '#prefix' => '', - '#suffix' => '', - ), - 'weight' => array( - '#type' => 'textfield', - '#default_value' => $group->weight, - '#size' => 3, - '#attributes' => array('class' => array('field-weight')), - ), - 'parent_wrapper' => array( - 'parent' => array( - '#type' => 'select', - '#options' => $parent_options, - '#empty_value' => '', - '#default_value' => isset($params->parents[$name]) ? $params->parents[$name] : '', - '#attributes' => array('class' => array('field-parent')), - '#parents' => array('fields', $name, 'parent'), - ), - 'hidden_name' => array( - '#type' => 'hidden', - '#default_value' => $name, - '#attributes' => array('class' => array('field-name')), - ), - ), - ); - - $table[$name] += array( - 'group_name' => array( - '#markup' => check_plain($name), - ), - 'format' => array( - 'type' => array( - '#type' => 'select', - '#options' => $formatter_options, - '#default_value' => $group->format_type, - '#attributes' => array('class' => array('field-group-type')), - ), - ), - ); - - $base_button = array( - '#submit' => array('field_ui_display_overview_multistep_submit'), - '#ajax' => array( - 'callback' => 'field_ui_display_overview_multistep_js', - 'wrapper' => 'field-display-overview-wrapper', - 'effect' => 'fade', - ), - '#field_name' => $name, - ); - - if ($form_state['formatter_settings_edit'] == $name) { - $table[$name]['format']['#cell_attributes'] = array('colspan' => $display_overview ? 3 : 3); - $table[$name]['format']['format_settings'] = array( - '#type' => 'container', - '#attributes' => array('class' => array('field-formatter-settings-edit-form')), - '#parents' => array('fields', $name, 'format_settings'), - '#weight' => -5, - 'label' => array( - '#markup' => t('Field group format:') . ' ' . $group->format_type . '', - ), - // Create a settings form where hooks can pick in. - 'settings' => $settings, - 'actions' => array( - '#type' => 'actions', - 'save_settings' => $base_button + array( - '#type' => 'submit', - '#name' => $name . '_formatter_settings_update', - '#value' => t('Update'), - '#op' => 'update', - ), - 'cancel_settings' => $base_button + array( - '#type' => 'submit', - '#name' => $name . '_formatter_settings_cancel', - '#value' => t('Cancel'), - '#op' => 'cancel', - // Do not check errors for the 'Cancel' button. - '#limit_validation_errors' => array(), - ), - ), - ); - $table[$name]['#attributes']['class'][] = 'field-formatter-settings-editing'; - $table[$name]['format']['type']['#attributes']['class'] = array('element-invisible'); - } - else { - // After saving, the settings are updated here aswell. First we create - // the element for the table cell. - $table[$name]['settings_summary'] = array('#markup' => ''); - if (!empty($group->format_settings)) { - $table[$name]['settings_summary'] = field_group_format_settings_summary($name, $group); - } - // Add the configure button. - $table[$name]['settings_edit'] = $base_button + array( - '#type' => 'image_button', - '#name' => $name . '_group_settings_edit', - '#src' => 'misc/configure.png', - '#attributes' => array('class' => array('field-formatter-settings-edit'), 'alt' => t('Edit')), - '#op' => 'edit', - // Do not check errors for the 'Edit' button. - '#limit_validation_errors' => array(), - '#prefix' => '
    ', - '#suffix' => '
    ', - ); - if ($display_overview) { - $table[$name]['settings_edit']['#suffix'] .= l(t('delete'), $params->admin_path . '/groups/' . $name . '/delete/' . $params->mode); - } - } - - if (!$display_overview) { - $table[$name] += array( - 'delete' => array( - '#markup' => l(t('delete'), $params->admin_path . '/groups/' . $name . '/delete/form'), - ), - ); - } - } - - // Additional row: add new group. - $parent_options = $table['#parent_options']; - unset($parent_options['_add_new_group']); - $table['_add_new_group'] = field_group_add_row('_add_new_group', $parent_options, $params); - - $table['_add_new_group'] += array( - 'format' => array( - 'type' => array( - '#type' => 'select', - '#options' => $formatter_options, - '#default_value' => 'fieldset', - ), - ), - 'settings_summary' => array(), - 'settings_edit' => array(), - ); - - if (!$display_overview) { - // See field_ui.admin.inc for more details on refresh rows. - $form['refresh_rows'] = array('#type' => 'hidden'); - $form['refresh'] = array( - '#type' => 'submit', - '#value' => t('Refresh'), - '#op' => 'refresh_table', - '#submit' => array('field_ui_display_overview_multistep_submit'), - '#ajax' => array( - 'callback' => 'field_ui_display_overview_multistep_js', - 'wrapper' => 'field-display-overview-wrapper', - 'effect' => 'fade', - // The button stays hidden, so we hide the AJAX spinner too. Ad-hoc - // spinners will be added manually by the client-side script. - 'progress' => 'none', - ), - ); - } - - $form['#attached']['css'][] = drupal_get_path('module', 'field_group') . '/field_group.field_ui.css'; - $form['#attached']['js'][] = drupal_get_path('module', 'field_group') . '/field_group.field_ui.js'; - - $form['#validate'][] = 'field_group_field_overview_validate'; - $form['#submit'][] = 'field_group_field_overview_submit'; - - // Create the settings for fieldgroup as vertical tabs (merged with DS). - field_group_field_ui_create_vertical_tabs($form, $form_state, $params); - -} - -/** - * Return an array of field_group_formatter options. - */ -function field_group_field_formatter_options($type) { - $options = &drupal_static(__FUNCTION__); - - if (!isset($options)) { - $options = array(); - $field_group_types = field_group_formatter_info(); - foreach ($field_group_types[$type] as $name => $field_group_type) { - $options[$name] = $field_group_type['label']; - } - } - return $options; -} - -/** - * Helper function to add a row in the overview forms. - */ -function field_group_add_row($name, $parent_options, $params) { - return array( - '#attributes' => array('class' => array('draggable', 'field-group', 'add-new')), - '#row_type' => 'add_new_group', - '#js_settings' => array('rowHandler' => 'group'), - '#region_callback' => $params->region_callback, - 'label' => array( - '#type' => 'textfield', - '#size' => 15, - '#description' => t('Label'), - '#prefix' => '
    ' . t('Add new group') . '
    ', - '#suffix' => '
    ', - ), - 'weight' => array( - '#type' => 'textfield', - '#default_value' => field_info_max_weight($params->entity_type, $params->bundle, $params->mode) + 3, - '#size' => 3, - '#title_display' => 'invisible', - '#title' => t('Weight for new group'), - '#attributes' => array('class' => array('field-weight')), - '#prefix' => '
     
    ', - ), - 'parent_wrapper' => array( - 'parent' => array( - '#type' => 'select', - '#options' => $parent_options, - '#empty_value' => '', - '#attributes' => array('class' => array('field-parent')), - '#prefix' => '
     
    ', - '#parents' => array('fields', $name, 'parent'), - ), - 'hidden_name' => array( - '#type' => 'hidden', - '#default_value' => $name, - '#attributes' => array('class' => array('field-name')), - ), - ), - 'group_name' => array( - '#type' => 'textfield', - // This field should stay LTR even for RTL languages. - '#field_prefix' => 'group_', - '#field_suffix' => '‎', - '#attributes' => array('dir' => 'ltr'), - '#size' => 15, - '#description' => t('Group name (a-z, 0-9, _)'), - '#prefix' => '
     
    ', - '#cell_attributes' => array('colspan' => $params->display_overview ? 1 : 2), - ), - ); -} - -/** - * Creates a form for field_group formatters. - * @param Object $group The FieldGroup object. - */ -function field_group_format_settings_form(&$group) { - $form = array(); - $form['label'] = array( - '#type' => 'textfield', - '#title' => t('Field group label'), - '#default_value' => $group->label, - '#weight' => -5, - '#element_validate' => array('field_group_format_settings_label_validate'), - ); - $form += module_invoke_all('field_group_format_settings', $group); - $form['#validate'] = array('field_group_format_settings_form_validate'); - return $form; -} - -/** - * Validate the label. Label is required for fieldsets that are collapsible. - */ -function field_group_format_settings_label_validate($element, &$form_state) { - - $group = $form_state['values']['fields'][$element['#parents'][1]]; - $settings = $group['format_settings']['settings']; - $name = $form_state['formatter_settings_edit']; - $form_state['values']['fields'][$name]['settings_edit_form']['settings'] = $settings; - if ($group['format']['type'] == 'fieldset' && ($settings['formatter'] == 'collapsed' || $settings['formatter'] == 'collapsible') && empty($settings['label'])) { - form_error($element, t('The label is required when formatter is collapsible or collapsed')); - } - -} - -/** - * Update the row so that the group variables are updated. - * The rendering of the elements needs the updated defaults. - * @param Object $group - * @param array $settings - */ -function field_group_formatter_row_update(& $group, $settings) { - // if the row has changed formatter type, update the group object - if (!empty($settings['format']['type']) && $settings['format']['type'] != $group->format_type) { - $group->format_type = $settings['format']['type']; - field_group_formatter_settings_update($group, $settings); - } -} - -/** - * Update handler for field_group configuration settings. - * @param Object $group The group object - * @param Array $settings Configuration settings - */ -function field_group_formatter_settings_update(& $group, $settings) { - - // Fetch the field_group formatter defaults. - $field_group_types = field_group_formatter_info(); - $mode = $group->mode == 'form' ? 'form' : 'display'; - $formatter = $field_group_types[$mode][$group->format_type]; - - // for format changes we load the defaults. - if (empty($settings['format_settings']['settings'])) { - $group->format_settings = array( - 'formatter' => isset($formatter['default_formatter']) ? $formatter['default_formatter'] : '', - 'instance_settings' => $formatter['instance_settings'] - ); - } - else { - $group->format_type = $settings['format']['type']; - $group->label = $settings['format_settings']['settings']['label']; - $group->format_settings = $settings['format_settings']['settings']; - // This is avoided now, cause of the form_state['field_group'] - // ctools_include('export'); - // ctools_export_crud_save('field_group', $group); - // cache_clear_all('field_groups', 'cache_field'); - } -} - -/** - * Creates a summary for the field format configuration summary. - * @param String $group_name The name of the group - * @param Object $group The group object - * @return Array ready to be rendered. - */ -function field_group_format_settings_summary($group_name, $group) { - $summary = implode('
    ', module_invoke_all('field_group_format_summary', $group)); - return array( - '#markup' => '
    ' . $summary . '
    ', - '#cell_attributes' => array('class' => array('field-formatter-summary-cell')), - ); -} - -/** - * Returns the region to which a row in the 'Manage fields' screen belongs. - * @param Array $row A field or field_group row - * @return String the current region. - */ -function field_group_field_overview_row_region($row) { - switch ($row['#row_type']) { - case 'group': - return 'main'; - case 'add_new_group': - // If no input in 'label', assume the row has not been dragged out of the - // 'add new' section. - if (empty($row['label']['#value'])) { - return 'add_new'; - } - return 'main'; - } -} - -/** - * Returns the region to which a row in the 'Manage display' screen belongs. - * @param Array $row A field or field_group row - * @return String the current region. - */ -function field_group_display_overview_row_region($row) { - switch ($row['#row_type']) { - case 'group': - return ($row['format']['type']['#value'] == 'hidden' ? 'hidden' : 'visible'); - case 'add_new_group': - // If no input in 'label', assume the row has not been dragged out of the - // 'add new' section. - if (empty($row['label']['#value'])) { - return 'add_new'; - } - return ($row['format']['type']['#value'] == 'hidden' ? 'hidden' : 'visible'); - } -} - -/** - * Validate handler for the overview screens. - * @param Array $form The complete form. - * @param Array $form_state The state of the form. - */ -function field_group_field_overview_validate($form, &$form_state) { - $form_values = $form_state['values']['fields']; - $entity_type = $form['#entity_type']; - $bundle = $form['#bundle']; - $mode = (isset($form['#view_mode']) ? $form['#view_mode'] : 'form'); - - $group = $form_values['_add_new_group']; - - // Validate if any information was provided in the 'add new group' row. - if (array_filter(array($group['label'], $group['group_name']))) { - // Missing label. - if (!$group['label']) { - form_set_error('fields][_add_new_group][label', t('Add new group: you need to provide a label.')); - } - - // Missing group name. - if (!$group['group_name']) { - form_set_error('fields][_add_new_group][group_name', t('Add new group: you need to provide a group name.')); - } - // Group name validation. - else { - $group_name = $group['group_name']; - - // Add the 'group_' prefix. - if (drupal_substr($group_name, 0, 6) != 'group_') { - $group_name = 'group_' . $group_name; - form_set_value($form['fields']['_add_new_group']['group_name'], $group_name, $form_state); - } - - // Invalid group name. - if (!preg_match('!^group_[a-z0-9_]+$!', $group_name)) { - form_set_error('fields][_add_new_group][group_name', t('Add new group: the group name %group_name is invalid. The name must include only lowercase unaccentuated letters, numbers, and underscores.', array('%group_name' => $group_name))); - } - if (drupal_strlen($group_name) > 32) { - form_set_error('fields][_add_new_group][group_name', t("Add new group: the group name %group_name is too long. The name is limited to 32 characters, including the 'group_' prefix.", array('%group_name' => $group_name))); - } - - // Group name already exists. - if (field_group_exists($group_name, $entity_type, $bundle, $mode)) { - form_set_error('fields][_add_new_group][group_name', t('Add new group: the group name %group_name already exists.', array('%group_name' => $group_name))); - } - } - } -} - -/** - * Submit handler for the overview screens. - * @param Array $form The complete form. - * @param Array $form_state The state of the form. - */ -function field_group_field_overview_submit($form, &$form_state) { - - $form_values = $form_state['values']['fields']; - $entity_type = $form['#entity_type']; - $bundle = $form['#bundle']; - $mode = (isset($form['#view_mode']) ? $form['#view_mode'] : 'form'); - - // Collect children. - $children = array_fill_keys($form['#groups'], array()); - foreach ($form_values as $name => $value) { - if (!empty($value['parent'])) { - // Substitute newly added fields, in case they were dragged - // directly in a group. - if ($name == '_add_new_field' && isset($form_state['fields_added']['_add_new_field'])) { - $name = $form_state['fields_added']['_add_new_field']; - } - elseif ($name == '_add_existing_field' && isset($form_state['fields_added']['_add_existing_field'])) { - $name = $form_state['fields_added']['_add_existing_field']; - } - $children[$value['parent']][$name] = $name; - } - } - - // Prepare storage with ctools. - ctools_include('export'); - - // Create new group. - if (!empty($form_values['_add_new_group']['group_name'])) { - $values = $form_values['_add_new_group']; - - $field_group_types = field_group_formatter_info(); - $formatter = $field_group_types[($mode == 'form' ? 'form' : 'display')][$values['format']['type']]; - - $new_group = (object) array( - 'identifier' => $values['group_name'] . '|' . $entity_type . '|' . $bundle . '|' . $mode, - 'group_name' => $values['group_name'], - 'entity_type' => $entity_type, - 'bundle' => $bundle, - 'mode' => $mode, - 'children' => isset($children['_add_new_group']) ? array_keys($children['_add_new_group']) : array(), - 'parent_name' => $values['parent'], - 'weight' => $values['weight'], - 'label' => $values['label'], - 'format_type' => $values['format']['type'], - 'disabled' => FALSE, - ); - $new_group->format_settings = array('formatter' => isset($formatter['default_formatter']) ? $formatter['default_formatter'] : ''); - if (isset($formatter['instance_settings'])) { - $new_group->format_settings['instance_settings'] = $formatter['instance_settings']; - } - - // Save and enable it in ctools - ctools_export_crud_save('field_group', $new_group); - ctools_export_crud_enable('field_group', $new_group->identifier); - - // Store new group information for any additional submit handlers. - $form_state['groups_added']['_add_new_group'] = $new_group->group_name; - drupal_set_message(t('New group %label successfully created.', array('%label' => $new_group->label))); - - // Replace the newly created group in the $children array, in case it was - // dragged directly in an existing field. - foreach (array_keys($children) as $parent) { - if (isset($children[$parent]['_add_new_group'])) { - unset($children[$parent]['_add_new_group']); - $children[$parent][$new_group->group_name] = $new_group->group_name; - } - } - } - - // Update existing groups. - $groups = field_group_info_groups($entity_type, $bundle, $mode, TRUE); - foreach ($form['#groups'] as $group_name) { - $group = $groups[$group_name]; - $group->label = $form_state['field_group'][$group_name]->label; - $group->children = array_keys($children[$group_name]); - $group->parent_name = $form_values[$group_name]['parent']; - $group->weight = $form_values[$group_name]['weight']; - $group->format_type = isset($form_values[$group_name]['format']['type']) ? $form_values[$group_name]['format']['type'] : 'visible'; - if (isset($form_state['field_group'][$group_name]->format_settings)) { - $group->format_settings = $form_state['field_group'][$group_name]->format_settings; - } - ctools_export_crud_save('field_group', $group); - } - - cache_clear_all('field_groups', 'cache_field'); -} - -/** - * Validate the entered css class from the submitted format settings. - * @param Array $element The validated element - * @param Array $form_state The state of the form. - */ -function field_group_validate_css_class($element, &$form_state) { - if (!empty($form_state['values']['fields'][$form_state['formatter_settings_edit']]['format_settings']['settings']['instance_settings']['classes']) && !preg_match('!^[A-Za-z0-9-_ ]+$!', $form_state['values']['fields'][$form_state['formatter_settings_edit']]['format_settings']['settings']['instance_settings']['classes'])) { - form_error($element, t('The css class must include only letters, numbers, underscores and dashes.')); - } -} - -/** - * Implements hook_field_info_max_weight(). - */ -function field_group_field_info_max_weight($entity_type, $bundle, $context) { - $weights = array(); - foreach (field_group_info_groups($entity_type, $bundle, $context) as $group) { - $weights[] = $group->weight; - } - return $weights ? max($weights) : NULL; -} - -/** - * Menu callback; present a form for removing a group. - */ -function field_group_delete_form($form, &$form_state, $group, $view_mode = 'form') { - - $form['#group'] = $group; - $admin_path = _field_ui_bundle_admin_path($group->entity_type, $group->bundle); - if ($view_mode == 'form') { - $admin_path .= '/fields'; - } - else { - $admin_path .= '/display/' . $view_mode; - } - $form['#redirect'] = array($admin_path); - $output = confirm_form($form, - t('Are you sure you want to delete the group %group?', array('%group' => t($group->label))), - $admin_path, - t('This action cannot be undone.'), - t('Delete'), t('Cancel'), - 'confirm' - ); - return $output; -} - -/** - * Remove group from bundle. - * - * @todo we'll have to reset all view mode settings - that will be fun :) - */ -function field_group_delete_form_submit($form, &$form_state) { - - $group = $form['#group']; - $bundle = $group->bundle; - $entity_type = $group->entity_type; - $group->mode = $form_state['build_info']['args'][1]; - - $bundles = field_info_bundles(); - $bundle_label = $bundles[$entity_type][$bundle]['label']; - - ctools_include('export'); - field_group_group_export_delete($group, FALSE); - - drupal_set_message(t('The group %group has been deleted from the %type content type.', array('%group' => t($group->label), '%type' => $bundle_label))); - - // Redirect. - $form_state['redirect'] = $form['#redirect']; -} - -/** - * Create vertical tabs. - */ -function field_group_field_ui_create_vertical_tabs(&$form, &$form_state, $params) { - - $form_state['field_group_params'] = $params; - - $entity_info = entity_get_info($params->entity_type); - $view_modes = array(); - if ($params->mode != 'default') { - $view_modes['default'] = t('Default'); - } - if ($params->mode != 'form') { - $view_modes['0'] = t('Form'); - } - foreach ($entity_info['view modes'] as $view_mode => $data) { - if ($data['custom settings'] && $params->mode != $view_mode) { - $view_modes[$view_mode] = $data['label']; - } - } - - // Add additional settings vertical tab. - if (!isset($form['additional_settings'])) { - $form['additional_settings'] = array( - '#type' => 'vertical_tabs', - '#theme_wrappers' => array('vertical_tabs'), - '#prefix' => '
    ', - '#suffix' => '
    ', - '#tree' => TRUE, - ); - $form['#attached']['js'][] = 'misc/form.js'; - $form['#attached']['js'][] = 'misc/collapse.js'; - } - - // Add extra guidelines for webmaster. - $form['additional_settings']['field_group'] = array( - '#type' => 'fieldset', - '#title' => t('Fieldgroups'), - '#description' => t('

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

    '), - '#collapsible' => TRUE, - '#collapsed' => FALSE, - '#parents' => array('additional_settings'), - ); - $form['additional_settings']['field_group']['fieldgroup_clone'] = array( - '#title' => t('Select source view mode or form'), - '#description' => t('Clone fieldgroups from selected view mode to the current display'), - '#type' => 'select', - '#options' => $view_modes, - '#default_value' => 'none' - ); - $form['additional_settings']['field_group']['fieldgroup_submit'] = array( - '#type' => 'submit', - '#value' => t('Clone'), - '#submit' => array('field_group_field_ui_clone_field_groups') - ); - -} - -/** - * Submit handler to save existing fieldgroups from one view mode or form to another. - */ -function field_group_field_ui_clone_field_groups($form, &$form_state) { - - $source_mode = $form_state['values']['additional_settings']['fieldgroup_clone'] == 0 ? 'form' : $form_state['values']['additional_settings']['fieldgroup_clone']; - $groups_to_clone = field_group_read_groups(array('bundle' => $form_state['field_group_params']->bundle, 'entity_type' => $form_state['field_group_params']->entity_type)); - $source_groups = $groups_to_clone[$form_state['field_group_params']->entity_type][$form_state['field_group_params']->bundle][$source_mode]; - - $fields = array_keys($form_state['values']['fields']); - - if (!empty($source_groups)) { - - foreach ($source_groups as $source_group) { - - if (in_array($source_group->group_name, $fields)) { - drupal_set_message(t('Fieldgroup @group is not cloned since a group already exists with the same name.', array('@group' => $source_group->group_name)), 'warning'); - continue; - } - - // Recreate the identifier and reset the id. - $source_group->id = NULL; - $source_group->mode = $form_state['field_group_params']->mode; - $source_group->identifier = $source_group->group_name . '|' . $source_group->entity_type . '|' . $source_group->bundle . '|' . $form_state['field_group_params']->mode; - $source_group->disabled = FALSE; - unset($source_group->export_type, $source_group->type, $source_group->table); - - // Save and enable it in ctools. - ctools_include('export'); - ctools_export_crud_save('field_group', $source_group); - ctools_export_crud_enable('field_group', $source_group->identifier); - - drupal_set_message(t('Fieldgroup @group cloned successfully.', array('@group' => $source_group->group_name))); - - } - } - -} -/** - * eof(). - */ \ No newline at end of file +entity_type = $form['#entity_type']; + $params->bundle = $form['#bundle']; + $params->admin_path = _field_ui_bundle_admin_path($params->entity_type, $params->bundle); + $params->display_overview = $display_overview; + + if ($display_overview) { + $params->region_callback = 'field_group_display_overview_row_region'; + $params->mode = $form['#view_mode']; + } + else { + $params->region_callback = 'field_group_field_overview_row_region'; + $params->mode = 'form'; + } + + $params->groups = field_group_info_groups($params->entity_type, $params->bundle, $params->mode, TRUE); + + // Gather parenting data. + $params->parents = array(); + foreach ($params->groups as $name => $group) { + foreach ($group->children as $child) { + $params->parents[$child] = $name; + } + } + + return $params; +} + +/** + * Function to alter the fields overview and display overview screen. + */ +function field_group_field_ui_overview_form_alter(&$form, &$form_state, $display_overview = FALSE) { + + // Only start altering the form if we need to. + if ($display_overview && empty($form['#fields']) && empty($form['#extra'])) { + return; + } + + $params = field_group_field_ui_form_params($form, $display_overview); + + // Add some things to be able to preserve synced usage of field_ui. + if (!$display_overview) { + // This key is used to store the current updated field. + $form_state += array( + 'formatter_settings_edit' => NULL, + ); + // Add AJAX wrapper. + $form['fields']['#prefix'] = '
    '; + $form['fields']['#suffix'] = '
    '; + } + $form['#groups'] = array_keys($params->groups); + + $table = &$form['fields']; + + // Add a region for 'add_new' rows, but only when fields are + // available and thus regions. + if (isset($table['#regions'])) { + $table['#regions'] += array( + 'add_new' => array('title' => ' '), + ); + } + + // Extend available parenting options. + foreach ($params->groups as $name => $group) { + $table['#parent_options'][$name] = $group->label; + } + $table['#parent_options']['_add_new_group'] = t('Add new group'); + + // Update existing rows accordingly to the parents. + foreach (element_children($table) as $name) { + $table[$name]['parent_wrapper']['parent']['#options'] = $table['#parent_options']; + // Inherit the value of the parent when default value is empty. + if (empty($table[$name]['parent_wrapper']['parent']['#default_value'])) { + $table[$name]['parent_wrapper']['parent']['#default_value'] = isset($params->parents[$name]) ? $params->parents[$name] : ''; + } + } + + $formatter_options = field_group_field_formatter_options($display_overview ? 'display' : 'form'); + + $refresh_rows = isset($form_state['values']['refresh_rows']) ? $form_state['values']['refresh_rows'] : (isset($form_state['input']['refresh_rows']) ? $form_state['input']['refresh_rows'] : NULL); + + // Create the group rows and check actions. + foreach (array_keys($params->groups) as $name) { + + // Play around with form_state so we only need to hold things + // between requests, until the save button was hit. + if (isset($form_state['field_group'][$name])) { + $group = &$form_state['field_group'][$name]; + } + else { + $group = &$params->groups[$name]; + } + + // Check the currently selected formatter, and merge persisted values for + // formatter settings for the group. + // This needs to be done first, so all fields are updated before creating form elements. + if (isset($refresh_rows) && $refresh_rows == $name) { + $settings = isset($form_state['values']['fields'][$name]) ? $form_state['values']['fields'][$name] : (isset($form_state['input']['fields'][$name]) ? $form_state['input']['fields'][$name] : NULL); + if (array_key_exists('settings_edit', $settings)) { + //$group->format_type = $form_state['field_group'][$name]->format_type; + $group = $form_state['field_group'][$name]; + } + field_group_formatter_row_update($group, $settings); + } + + // Save the group when the configuration is submitted. + if (!empty($form_state['values'][$name . '_formatter_settings_update'])) { + field_group_formatter_settings_update($group, $form_state['values']['fields'][$name]); + } + // After all updates are finished, let the form_state know. + $form_state['field_group'][$name] = $group; + + $settings = field_group_format_settings_form($group); + + $id = strtr($name, '_', '-'); + $js_rows_data[$id] = array('type' => 'group', 'name' => $name); + // A group cannot be selected as its own parent. + $parent_options = $table['#parent_options']; + unset($parent_options[$name]); + $table[$name] = array( + '#attributes' => array('class' => array('draggable', 'field-group'), 'id' => $id), + '#row_type' => 'group', + '#region_callback' => $params->region_callback, + '#js_settings' => array('rowHandler' => 'group'), + 'human_name' => array( + '#markup' => check_plain(t($group->label)), + '#prefix' => '', + '#suffix' => '', + ), + 'weight' => array( + '#type' => 'textfield', + '#default_value' => $group->weight, + '#size' => 3, + '#attributes' => array('class' => array('field-weight')), + ), + 'parent_wrapper' => array( + 'parent' => array( + '#type' => 'select', + '#options' => $parent_options, + '#empty_value' => '', + '#default_value' => isset($params->parents[$name]) ? $params->parents[$name] : '', + '#attributes' => array('class' => array('field-parent')), + '#parents' => array('fields', $name, 'parent'), + ), + 'hidden_name' => array( + '#type' => 'hidden', + '#default_value' => $name, + '#attributes' => array('class' => array('field-name')), + ), + ), + ); + + $table[$name] += array( + 'group_name' => array( + '#markup' => check_plain($name), + ), + 'format' => array( + 'type' => array( + '#type' => 'select', + '#options' => $formatter_options, + '#default_value' => $group->format_type, + '#attributes' => array('class' => array('field-group-type')), + ), + ), + ); + + $base_button = array( + '#submit' => array('field_ui_display_overview_multistep_submit'), + '#ajax' => array( + 'callback' => 'field_ui_display_overview_multistep_js', + 'wrapper' => 'field-display-overview-wrapper', + 'effect' => 'fade', + ), + '#field_name' => $name, + ); + + if ($form_state['formatter_settings_edit'] == $name) { + $table[$name]['format']['#cell_attributes'] = array('colspan' => $display_overview ? 3 : 3); + $table[$name]['format']['format_settings'] = array( + '#type' => 'container', + '#attributes' => array('class' => array('field-formatter-settings-edit-form')), + '#parents' => array('fields', $name, 'format_settings'), + '#weight' => -5, + 'label' => array( + '#markup' => t('Field group format:') . ' ' . $group->format_type . '', + ), + // Create a settings form where hooks can pick in. + 'settings' => $settings, + 'actions' => array( + '#type' => 'actions', + 'save_settings' => $base_button + array( + '#type' => 'submit', + '#name' => $name . '_formatter_settings_update', + '#value' => t('Update'), + '#op' => 'update', + ), + 'cancel_settings' => $base_button + array( + '#type' => 'submit', + '#name' => $name . '_formatter_settings_cancel', + '#value' => t('Cancel'), + '#op' => 'cancel', + // Do not check errors for the 'Cancel' button. + '#limit_validation_errors' => array(), + ), + ), + ); + $table[$name]['#attributes']['class'][] = 'field-formatter-settings-editing'; + $table[$name]['format']['type']['#attributes']['class'] = array('element-invisible'); + } + else { + // After saving, the settings are updated here as well. First we create + // the element for the table cell. + $table[$name]['settings_summary'] = array('#markup' => ''); + if (!empty($group->format_settings)) { + $table[$name]['settings_summary'] = field_group_format_settings_summary($name, $group); + } + // Add the configure button. + $table[$name]['settings_edit'] = $base_button + array( + '#type' => 'image_button', + '#name' => $name . '_group_settings_edit', + '#src' => 'misc/configure.png', + '#attributes' => array('class' => array('field-formatter-settings-edit'), 'alt' => t('Edit')), + '#op' => 'edit', + // Do not check errors for the 'Edit' button. + '#limit_validation_errors' => array(), + '#prefix' => '
    ', + '#suffix' => '
    ', + ); + if ($display_overview) { + $table[$name]['settings_edit']['#suffix'] .= l(t('delete'), $params->admin_path . '/groups/' . $name . '/delete/' . $params->mode); + } + } + + if (!$display_overview) { + $table[$name] += array( + 'delete' => array( + '#markup' => l(t('delete'), $params->admin_path . '/groups/' . $name . '/delete/form'), + ), + ); + } + } + + // Additional row: add new group. + $parent_options = $table['#parent_options']; + unset($parent_options['_add_new_group']); + $table['_add_new_group'] = field_group_add_row('_add_new_group', $parent_options, $params); + + $table['_add_new_group'] += array( + 'format' => array( + 'type' => array( + '#type' => 'select', + '#options' => $formatter_options, + '#default_value' => 'fieldset', + '#prefix' => '
     
    ', + ), + '#cell_attributes' => array('colspan' => 3), + ), + ); + + if (!$display_overview) { + // See field_ui.admin.inc for more details on refresh rows. + $form['refresh_rows'] = array('#type' => 'hidden'); + $form['refresh'] = array( + '#type' => 'submit', + '#value' => t('Refresh'), + '#op' => 'refresh_table', + '#submit' => array('field_ui_display_overview_multistep_submit'), + '#ajax' => array( + 'callback' => 'field_ui_display_overview_multistep_js', + 'wrapper' => 'field-display-overview-wrapper', + 'effect' => 'fade', + // The button stays hidden, so we hide the AJAX spinner too. Ad-hoc + // spinners will be added manually by the client-side script. + 'progress' => 'none', + ), + ); + } + + $form['#attached']['css'][] = drupal_get_path('module', 'field_group') . '/field_group.field_ui.css'; + $form['#attached']['js'][] = drupal_get_path('module', 'field_group') . '/field_group.field_ui.js'; + + $form['#validate'][] = 'field_group_field_overview_validate'; + $form['#submit'][] = 'field_group_field_overview_submit'; + + // Create the settings for fieldgroup as vertical tabs (merged with DS). + field_group_field_ui_create_vertical_tabs($form, $form_state, $params); + + // Show a warning if the user has not set up required containers + if ($form['#groups']) { + + $parent_requirements = array( + 'multipage' => array( + 'parent' => 'multipage-group', + 'message' => 'Each Multipage element needs to have a parent Multipage group element.', + ), + 'htab' => array( + 'parent' => 'htabs', + 'message' => 'Each Horizontal tab element needs to have a parent Horizontal tabs group element.', + ), + 'accordion-item' => array( + 'parent' => 'accordion', + 'message' => 'Each Accordion item element needs to have a parent Accordion group element.', + ), + ); + + // On display overview tabs need to be checked. + if ($display_overview) { + $parent_requirements['tab'] = array( + 'parent' => 'tabs', + 'message' => 'Each Vertical tab element needs to have a parent Vertical tabs group element.', + ); + } + + foreach ($form['#groups'] as $group_name) { + $group_check = field_group_load_field_group($group_name, $params->entity_type, $params->bundle, $params->mode); + if (isset($parent_requirements[$group_check->format_type])) { + if (!$group_check->parent_name || field_group_load_field_group($group_check->parent_name, $params->entity_type, $params->bundle, $params->mode)->format_type != $parent_requirements[$group_check->format_type]['parent']) { + drupal_set_message(t($parent_requirements[$group_check->format_type]['message']), 'warning', FALSE); + } + } + } + } +} + +/** + * Return an array of field_group_formatter options. + */ +function field_group_field_formatter_options($type) { + $options = &drupal_static(__FUNCTION__); + + if (!isset($options)) { + $options = array(); + $field_group_types = field_group_formatter_info(); + foreach ($field_group_types[$type] as $name => $field_group_type) { + $options[$name] = $field_group_type['label']; + } + } + return $options; +} + +/** + * Helper function to add a row in the overview forms. + */ +function field_group_add_row($name, $parent_options, $params) { + return array( + '#attributes' => array('class' => array('draggable', 'field-group', 'add-new')), + '#row_type' => 'add_new_group', + '#js_settings' => array('rowHandler' => 'group'), + '#region_callback' => $params->region_callback, + 'label' => array( + '#title_display' => 'invisible', + '#title' => t('Label for new group'), + '#type' => 'textfield', + '#size' => 15, + '#description' => t('Label'), + '#prefix' => '
    ' . t('Add new group') . '
    ', + '#suffix' => '
    ', + ), + 'weight' => array( + '#type' => 'textfield', + '#default_value' => field_info_max_weight($params->entity_type, $params->bundle, $params->mode) + 3, + '#size' => 3, + '#title_display' => 'invisible', + '#title' => t('Weight for new group'), + '#attributes' => array('class' => array('field-weight')), + '#prefix' => '
     
    ', + ), + 'parent_wrapper' => array( + 'parent' => array( + '#title_display' => 'invisible', + '#title' => t('Parent for new group'), + '#type' => 'select', + '#options' => $parent_options, + '#empty_value' => '', + '#attributes' => array('class' => array('field-parent')), + '#prefix' => '
     
    ', + '#parents' => array('fields', $name, 'parent'), + ), + 'hidden_name' => array( + '#type' => 'hidden', + '#default_value' => $name, + '#attributes' => array('class' => array('field-name')), + ), + ), + 'group_name' => array( + '#type' => 'textfield', + '#title_display' => 'invisible', + '#title' => t('Machine name for new group'), + // This field should stay LTR even for RTL languages. + '#field_prefix' => 'group_', + '#field_suffix' => '‎', + '#attributes' => array('dir' => 'ltr'), + '#size' => 15, + '#description' => t('Group name (a-z, 0-9, _)'), + '#prefix' => '
     
    ', + '#cell_attributes' => array('colspan' => $params->display_overview ? 1 : 2), + ), + ); +} + +/** + * Creates a form for field_group formatters. + * @param Object $group The FieldGroup object. + */ +function field_group_format_settings_form(&$group) { + $form = array(); + $form['label'] = array( + '#type' => 'textfield', + '#title' => t('Field group label'), + '#default_value' => $group->label, + '#weight' => -5, + '#element_validate' => array('field_group_format_settings_label_validate'), + ); + $addition = module_invoke_all('field_group_format_settings', $group); + $form += $addition; + // Give fieldgroup implementations the chance to alter the description. + if (!empty($addition['label']['#description'])) { + $form['label']['#description'] = $addition['label']['#description']; + } + $form['#validate'] = array('field_group_format_settings_form_validate'); + return $form; +} + +/** + * Validate the label. Label is required for fieldsets that are collapsible. + */ +function field_group_format_settings_label_validate($element, &$form_state) { + + $group = $form_state['values']['fields'][$element['#parents'][1]]; + $settings = $group['format_settings']['settings']; + $name = $form_state['formatter_settings_edit']; + $form_state['values']['fields'][$name]['settings_edit_form']['settings'] = $settings; + if ($group['format']['type'] == 'fieldset' && ($settings['formatter'] == 'collapsed' || $settings['formatter'] == 'collapsible') && empty($settings['label'])) { + form_error($element, t('The label is required when formatter is collapsible or collapsed')); + } + +} + +/** + * Update the row so that the group variables are updated. + * The rendering of the elements needs the updated defaults. + * @param Object $group + * @param array $settings + */ +function field_group_formatter_row_update(&$group, $settings) { + // if the row has changed formatter type, update the group object + if (!empty($settings['format']['type']) && $settings['format']['type'] != $group->format_type) { + $group->format_type = $settings['format']['type']; + field_group_formatter_settings_update($group, $settings); + } +} + +/** + * Update handler for field_group configuration settings. + * @param Object $group The group object + * @param Array $settings Configuration settings + */ +function field_group_formatter_settings_update(&$group, $settings) { + + // for format changes we load the defaults. + if (empty($settings['format_settings']['settings'])) { + $mode = $group->mode == 'form' ? 'form' : 'display'; + $group->format_settings = _field_group_get_default_formatter_settings($group->format_type, $mode); + } + else { + $group->format_type = $settings['format']['type']; + $group->label = $settings['format_settings']['settings']['label']; + $group->format_settings = $settings['format_settings']['settings']; + } +} + +/** + * Creates a summary for the field format configuration summary. + * @param String $group_name The name of the group + * @param Object $group The group object + * @return Array ready to be rendered. + */ +function field_group_format_settings_summary($group_name, $group) { + $summary = implode('
    ', module_invoke_all('field_group_format_summary', $group)); + return array( + '#markup' => '
    ' . $summary . '
    ', + '#cell_attributes' => array('class' => array('field-formatter-summary-cell')), + ); +} + +/** + * Returns the region to which a row in the 'Manage fields' screen belongs. + * @param Array $row A field or field_group row + * @return String the current region. + */ +function field_group_field_overview_row_region($row) { + switch ($row['#row_type']) { + case 'group': + return 'main'; + case 'add_new_group': + // If no input in 'label', assume the row has not been dragged out of the + // 'add new' section. + if (empty($row['label']['#value'])) { + return 'add_new'; + } + return 'main'; + } +} + +/** + * Returns the region to which a row in the 'Manage display' screen belongs. + * @param Array $row A field or field_group row + * @return String the current region. + */ +function field_group_display_overview_row_region($row) { + switch ($row['#row_type']) { + case 'group': + return ($row['format']['type']['#value'] == 'hidden' ? 'hidden' : 'visible'); + case 'add_new_group': + // If no input in 'label', assume the row has not been dragged out of the + // 'add new' section. + if (empty($row['label']['#value'])) { + return 'add_new'; + } + return ($row['format']['type']['#value'] == 'hidden' ? 'hidden' : 'visible'); + } +} + +/** + * Validate handler for the overview screens. + * @param Array $form The complete form. + * @param Array $form_state The state of the form. + */ +function field_group_field_overview_validate($form, &$form_state) { + $form_values = $form_state['values']['fields']; + $entity_type = $form['#entity_type']; + $bundle = $form['#bundle']; + $mode = (isset($form['#view_mode']) ? $form['#view_mode'] : 'form'); + + $group = $form_values['_add_new_group']; + + // Validate if any information was provided in the 'add new group' row. + if (array_filter(array($group['label'], $group['group_name']))) { + + // Missing group name. + if (!$group['group_name']) { + form_set_error('fields][_add_new_group][group_name', t('Add new group: you need to provide a group name.')); + } + // Group name validation. + else { + $group_name = $group['group_name']; + + // Add the 'group_' prefix. + if (drupal_substr($group_name, 0, 6) != 'group_') { + $group_name = 'group_' . $group_name; + form_set_value($form['fields']['_add_new_group']['group_name'], $group_name, $form_state); + } + + // Invalid group name. + if (!preg_match('!^group_[a-z0-9_]+$!', $group_name)) { + form_set_error('fields][_add_new_group][group_name', t('Add new group: the group name %group_name is invalid. The name must include only lowercase unaccentuated letters, numbers, and underscores.', array('%group_name' => $group_name))); + } + if (drupal_strlen($group_name) > 32) { + form_set_error('fields][_add_new_group][group_name', t("Add new group: the group name %group_name is too long. The name is limited to 32 characters, including the 'group_' prefix.", array('%group_name' => $group_name))); + } + + // Group name already exists. + if (field_group_exists($group_name, $entity_type, $bundle, $mode)) { + form_set_error('fields][_add_new_group][group_name', t('Add new group: the group name %group_name already exists.', array('%group_name' => $group_name))); + } + } + } +} + +/** + * Submit handler for the overview screens. + * @param Array $form The complete form. + * @param Array $form_state The state of the form. + */ +function field_group_field_overview_submit($form, &$form_state) { + + $form_values = $form_state['values']['fields']; + $entity_type = $form['#entity_type']; + $bundle = $form['#bundle']; + $mode = (isset($form['#view_mode']) ? $form['#view_mode'] : 'form'); + + // Collect children. + $children = array_fill_keys($form['#groups'], array()); + foreach ($form_values as $name => $value) { + if (!empty($value['parent'])) { + // Substitute newly added fields, in case they were dragged + // directly in a group. + if ($name == '_add_new_field' && isset($form_state['fields_added']['_add_new_field'])) { + $name = $form_state['fields_added']['_add_new_field']; + } + elseif ($name == '_add_existing_field' && isset($form_state['fields_added']['_add_existing_field'])) { + $name = $form_state['fields_added']['_add_existing_field']; + } + $children[$value['parent']][$name] = $name; + } + } + + // Prepare storage with ctools. + ctools_include('export'); + + // Create new group. + if (!empty($form_values['_add_new_group']['group_name'])) { + $values = $form_values['_add_new_group']; + + $field_group_types = field_group_formatter_info(); + $formatter = $field_group_types[($mode == 'form' ? 'form' : 'display')][$values['format']['type']]; + + $new_group = (object) array( + 'identifier' => $values['group_name'] . '|' . $entity_type . '|' . $bundle . '|' . $mode, + 'group_name' => $values['group_name'], + 'entity_type' => $entity_type, + 'bundle' => $bundle, + 'mode' => $mode, + 'children' => isset($children['_add_new_group']) ? array_keys($children['_add_new_group']) : array(), + 'parent_name' => $values['parent'], + 'weight' => $values['weight'], + 'label' => $values['label'], + 'format_type' => $values['format']['type'], + 'disabled' => FALSE, + ); + $new_group->format_settings = array('formatter' => isset($formatter['default_formatter']) ? $formatter['default_formatter'] : ''); + if (isset($formatter['instance_settings'])) { + $new_group->format_settings['instance_settings'] = $formatter['instance_settings']; + } + + $classes = _field_group_get_html_classes($new_group); + $new_group->format_settings['instance_settings']['classes'] = implode(' ', $classes->optional); + + // Save and enable it in ctools. + ctools_export_crud_save('field_group', $new_group); + ctools_export_crud_enable('field_group', $new_group->identifier); + + // Store new group information for any additional submit handlers. + $form_state['groups_added']['_add_new_group'] = $new_group->group_name; + drupal_set_message(t('New group %label successfully created.', array('%label' => $new_group->label))); + + // Replace the newly created group in the $children array, in case it was + // dragged directly in an existing field. + foreach (array_keys($children) as $parent) { + if (isset($children[$parent]['_add_new_group'])) { + unset($children[$parent]['_add_new_group']); + $children[$parent][$new_group->group_name] = $new_group->group_name; + } + } + } + + // Update existing groups. + $groups = field_group_info_groups($entity_type, $bundle, $mode, TRUE); + foreach ($form['#groups'] as $group_name) { + $group = $groups[$group_name]; + $group->label = $form_state['field_group'][$group_name]->label; + $group->children = array_keys($children[$group_name]); + $group->parent_name = $form_values[$group_name]['parent']; + $group->weight = $form_values[$group_name]['weight']; + + $old_format_type = $group->format_type; + $group->format_type = isset($form_values[$group_name]['format']['type']) ? $form_values[$group_name]['format']['type'] : 'visible'; + if (isset($form_state['field_group'][$group_name]->format_settings)) { + $group->format_settings = $form_state['field_group'][$group_name]->format_settings; + } + + // If the format type is changed, make sure we have all required format settings. + if ($group->format_type != $old_format_type) { + $mode = $group->mode == 'form' ? 'form' : 'display'; + $default_formatter_settings = _field_group_get_default_formatter_settings($group->format_type, $mode); + $group->format_settings += $default_formatter_settings; + $group->format_settings['instance_settings'] += $default_formatter_settings['instance_settings']; + } + + ctools_export_crud_save('field_group', $group); + } + + cache_clear_all('field_groups', 'cache_field'); +} + +/** + * Validate the entered css class from the submitted format settings. + * @param Array $element The validated element + * @param Array $form_state The state of the form. + */ +function field_group_validate_css_class($element, &$form_state) { + if (!empty($form_state['values']['fields'][$form_state['formatter_settings_edit']]['format_settings']['settings']['instance_settings']['classes']) && !preg_match('!^[A-Za-z0-9-_ ]+$!', $form_state['values']['fields'][$form_state['formatter_settings_edit']]['format_settings']['settings']['instance_settings']['classes'])) { + form_error($element, t('The css class must include only letters, numbers, underscores and dashes.')); + } +} + +/** + * Validate the entered id attribute from the submitted format settings. + * @param Array $element The validated element + * @param Array $form_state The state of the form. + */ +function field_group_validate_id($element, &$form_state) { + if (!empty($form_state['values']['fields'][$form_state['formatter_settings_edit']]['format_settings']['settings']['instance_settings']['id']) && !preg_match('!^[A-Za-z0-9-_]+$!', $form_state['values']['fields'][$form_state['formatter_settings_edit']]['format_settings']['settings']['instance_settings']['id'])) { + form_error($element, t('The id must include only letters, numbers, underscores and dashes.')); + } +} + +/** + * Implements hook_field_info_max_weight(). + */ +function field_group_field_info_max_weight($entity_type, $bundle, $context) { + $weights = array(); + foreach (field_group_info_groups($entity_type, $bundle, $context) as $group) { + $weights[] = $group->weight; + } + return $weights ? max($weights) : NULL; +} + +/** + * Menu callback; present a form for removing a group. + */ +function field_group_delete_form($form, &$form_state, $group, $view_mode = 'form') { + + $form['#group'] = $group; + $admin_path = _field_ui_bundle_admin_path($group->entity_type, $group->bundle); + if ($view_mode == 'form') { + $admin_path .= '/fields'; + } + else { + $admin_path .= '/display/' . $view_mode; + } + $form['#redirect'] = array($admin_path); + $output = confirm_form($form, + t('Are you sure you want to delete the group %group?', array('%group' => t($group->label))), + $admin_path, + t('This action cannot be undone.'), + t('Delete'), t('Cancel'), + 'confirm' + ); + return $output; +} + +/** + * Remove group from bundle. + * + * @todo we'll have to reset all view mode settings - that will be fun :) + */ +function field_group_delete_form_submit($form, &$form_state) { + + $group = $form['#group']; + $bundle = $group->bundle; + $entity_type = $group->entity_type; + $group->mode = $form_state['build_info']['args'][1]; + + $bundles = field_info_bundles(); + $bundle_label = $bundles[$entity_type][$bundle]['label']; + + ctools_include('export'); + field_group_group_export_delete($group, FALSE); + + drupal_set_message(t('The group %group has been deleted from the %type content type.', array('%group' => t($group->label), '%type' => $bundle_label))); + + // Redirect. + $form_state['redirect'] = $form['#redirect']; +} + +/** + * Menu callback; present a form for re-enabling a group. + */ +function field_group_enable_form($form, &$form_state, $group, $view_mode = 'form') { + + $form['#group'] = $group; + $admin_path = _field_ui_bundle_admin_path($group->entity_type, $group->bundle); + if ($view_mode == 'form') { + $admin_path .= '/fields'; + } + else { + $admin_path .= '/display/' . $view_mode; + } + $form['#redirect'] = array($admin_path); + $output = confirm_form($form, + t('Are you sure you want to enable the group %group?', array('%group' => t($group->label))), + $admin_path, + '', + t('Enable'), t('Cancel'), + 'confirm' + ); + return $output; +} + +/** + * Re-enable the group on a bundle. + */ +function field_group_enable_form_submit($form, &$form_state) { + + $group = $form['#group']; + $bundle = $group->bundle; + $entity_type = $group->entity_type; + $group->mode = $form_state['build_info']['args'][1]; + + $bundles = field_info_bundles(); + $bundle_label = $bundles[$entity_type][$bundle]['label']; + + ctools_include('export'); + ctools_export_crud_enable('field_group', $group->identifier); + + drupal_set_message(t('The group %group has been enabled on the %type content type.', array('%group' => t($group->label), '%type' => $bundle_label))); + + // Redirect. + $form_state['redirect'] = $form['#redirect']; +} + +/** + * Create vertical tabs. + */ +function field_group_field_ui_create_vertical_tabs(&$form, &$form_state, $params) { + + $form_state['field_group_params'] = $params; + + $entity_info = entity_get_info($params->entity_type); + $view_modes = array(); + if ($params->mode != 'default') { + $view_modes['default'] = t('Default'); + } + if ($params->mode != 'form') { + $view_modes['0'] = t('Form'); + } + foreach ($entity_info['view modes'] as $view_mode => $data) { + if ($data['custom settings'] && $params->mode != $view_mode) { + $view_modes[$view_mode] = $data['label']; + } + } + + // Add additional settings vertical tab. + if (!isset($form['additional_settings'])) { + $form['additional_settings'] = array( + '#type' => 'vertical_tabs', + '#theme_wrappers' => array('vertical_tabs'), + '#prefix' => '
    ', + '#suffix' => '
    ', + '#tree' => TRUE, + ); + $form['#attached']['js'][] = 'misc/form.js'; + $form['#attached']['js'][] = 'misc/collapse.js'; + } + + // Add extra guidelines for webmaster. + $form['additional_settings']['field_group'] = array( + '#type' => 'fieldset', + '#title' => t('Fieldgroups'), + '#description' => t('

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

    '), + '#collapsible' => TRUE, + '#collapsed' => FALSE, + '#parents' => array('additional_settings'), + ); + $form['additional_settings']['field_group']['fieldgroup_clone'] = array( + '#title' => t('Select source view mode or form'), + '#description' => t('Clone fieldgroups from selected view mode to the current display'), + '#type' => 'select', + '#options' => $view_modes, + '#default_value' => 'none' + ); + $form['additional_settings']['field_group']['fieldgroup_submit'] = array( + '#type' => 'submit', + '#value' => t('Clone'), + '#validate' => array('field_group_field_ui_clone_field_groups_validate'), + '#submit' => array('field_group_field_ui_clone_field_groups') + ); + + $disabled_groups = field_group_read_groups(array(), FALSE); + + // Show disabled fieldgroups, and make it possible to enable them again. + if ($disabled_groups && isset($disabled_groups[$params->entity_type][$params->bundle][$params->mode])) { + $form['additional_settings']['disabled_field_groups'] = array( + + '#type' => 'fieldset', + + '#title' => t('Disabled fieldgroups'), + + '#collapsible' => TRUE, + + '#collapsed' => FALSE, + + '#parents' => array('additional_settings'), + + ); + $form['additional_settings']['disabled_field_groups']['overview'] = field_group_disabled_groups_overview($disabled_groups[$params->entity_type][$params->bundle][$params->mode], $entity_info, $params); + } + +} + +/** + * Validate handler to validate saving existing fieldgroups from one view mode or form to another. + */ +function field_group_field_ui_clone_field_groups_validate($form, &$form_state) { + + $source_mode = $form_state['#source_mode'] = $form_state['values']['additional_settings']['fieldgroup_clone'] == '0' ? 'form' : $form_state['values']['additional_settings']['fieldgroup_clone']; + $groups_to_clone = $form_state['#groups_to_clone'] = field_group_read_groups(array('bundle' => $form_state['field_group_params']->bundle, 'entity_type' => $form_state['field_group_params']->entity_type)); + + $form_state['#source_groups'] = array(); + if (!empty($groups_to_clone) && isset($groups_to_clone[$form_state['field_group_params']->entity_type], $groups_to_clone[$form_state['field_group_params']->entity_type][$form_state['field_group_params']->bundle], $groups_to_clone[$form_state['field_group_params']->entity_type][$form_state['field_group_params']->bundle][$source_mode])) { + $form_state['#source_groups'] = $groups_to_clone[$form_state['field_group_params']->entity_type][$form_state['field_group_params']->bundle][$source_mode]; + } + + // Check for types are not known in current mode. + if ($form_state['field_group_params']->mode != 'form') { + $non_existing_types = array('multipage', 'multipage-group'); + } + else { + $non_existing_types = array('div'); + } + + foreach ($form_state['#source_groups'] as $key => $group) { + if (in_array($group->format_type, $non_existing_types)) { + unset($form_state['#source_groups'][$key]); + drupal_set_message(t('Skipping @group because this type does not exist in current mode', array('@group' => $group->label)), 'warning'); + } + } + + if (empty($form_state['#source_groups'])) { + // Report error found with selection. + form_set_error('additional_settings][fieldgroup_clone', t('No field groups were found in selected view mode.')); + return; + } + +} + +/** + * Submit handler to save existing fieldgroups from one view mode or form to another. + */ +function field_group_field_ui_clone_field_groups($form, &$form_state) { + + $source_mode = $form_state['#source_mode']; + $groups_to_clone = $form_state['#groups_to_clone']; + + $fields = array_keys($form_state['values']['fields']); + if (!empty($form_state['#source_groups'])) { + + foreach ($form_state['#source_groups'] as $source_group) { + if (in_array($source_group->group_name, $fields)) { + drupal_set_message(t('Fieldgroup @group is not cloned since a group already exists with the same name.', array('@group' => $source_group->group_name)), 'warning'); + continue; + } + + // Recreate the identifier and reset the id. + $source_group->id = NULL; + $source_group->mode = $form_state['field_group_params']->mode; + $source_group->identifier = $source_group->group_name . '|' . $source_group->entity_type . '|' . $source_group->bundle . '|' . $form_state['field_group_params']->mode; + $source_group->disabled = FALSE; + $source_group->children = array(); + unset($source_group->export_type, $source_group->type, $source_group->table); + + // Save and enable it in ctools. + ctools_include('export'); + ctools_export_crud_save('field_group', $source_group); + ctools_export_crud_enable('field_group', $source_group->identifier); + + drupal_set_message(t('Fieldgroup @group cloned successfully.', array('@group' => $source_group->group_name))); + + } + } + +} + +/** + * Show an overview of all the disabled fieldgroups, and make it possible to activate them again. + * @param $disabled_groups Array with all disabled groups. + */ +function field_group_disabled_groups_overview($disabled_groups, $entity_info, $params) { + + $formatter_options = field_group_field_formatter_options($params->mode != 'form' ? 'display' : 'form'); + + $table = array( + '#theme' => 'table', + '#header' => array( + t('Label'), + t('Machine name'), + t('Field'), + t('Widget'), + t('Operations'), + ), + '#attributes' => array( + 'class' => array('field-ui-overview'), + ), + '#rows' => array(), + ); + + // Add all of the disabled groups as a row on the table. + foreach ($disabled_groups as $group) { + + $summary = field_group_format_settings_summary($group->group_name, $group); + + $row = array(); + $row[] = $group->label; + $row[] = $group->group_name; + $row[] = $formatter_options[$group->format_type]; + $row[] = render($summary); + $path = (isset($entity_info['bundles'][$params->bundle]['admin']['real path']) ? $entity_info['bundles'][$params->bundle]['admin']['real path'] : $entity_info['bundles'][$params->bundle]['admin']['path']); + $row[] = l(t('Enable'), $path . '/groups/' . $group->group_name . '/enable/' . $group->mode); + + $table['#rows'][] = $row; + } + + return $table; + +} +/** + * eof(). + */ diff --git a/sites/all/modules/contrib/fields/field_group/field_group.field_ui.js b/sites/all/modules/contrib/fields/field_group/field_group.field_ui.js old mode 100755 new mode 100644 diff --git a/sites/all/modules/contrib/fields/field_group/field_group.info b/sites/all/modules/contrib/fields/field_group/field_group.info index efab892..49eae8d 100644 --- a/sites/all/modules/contrib/fields/field_group/field_group.info +++ b/sites/all/modules/contrib/fields/field_group/field_group.info @@ -1,18 +1,14 @@ -name = Fieldgroup -description = Fieldgroup +name = Field Group +description = "Provides the ability to group your fields on both form and display." package = Fields dependencies[] = field dependencies[] = ctools core = 7.x -files[] = field_group.install -files[] = field_group.module -files[] = field_group.field_ui.inc -files[] = field_group.form.inc -files[] = field_group.features.inc -files[] = field_group.test -; Information added by drupal.org packaging script on 2011-10-19 -version = "7.x-1.1" +files[] = tests/field_group.ui.test +files[] = tests/field_group.display.test +; Information added by Drupal.org packaging script on 2017-11-03 +version = "7.x-1.6" core = "7.x" project = "field_group" -datestamp = "1319051133" +datestamp = "1509751991" diff --git a/sites/all/modules/contrib/fields/field_group/field_group.install b/sites/all/modules/contrib/fields/field_group/field_group.install old mode 100755 new mode 100644 index b84b435..a7bb85c --- a/sites/all/modules/contrib/fields/field_group/field_group.install +++ b/sites/all/modules/contrib/fields/field_group/field_group.install @@ -17,6 +17,7 @@ function field_group_schema() { 'key' => 'identifier', 'identifier' => 'field_group', 'default hook' => 'field_group_info', + 'load callback' => 'field_group_group_load_by_identifier', 'save callback' => 'field_group_group_save', 'delete callback' => 'field_group_group_export_delete', 'can disable' => TRUE, @@ -108,14 +109,14 @@ function _field_group_install_read_groups() { ->condition('group_type', 'standard'); foreach ($query->execute() as $record) { $record['settings'] = unserialize($record['settings']); - $groups[$record['group_name']] = $record; + $groups[$record['group_name'] . '-' . $record['type_name']] = $record; } foreach ($groups as $key => $group) { $query2 = db_select('content_group_fields', 'cgf', array('fetch' => PDO::FETCH_ASSOC)) ->fields('cgf') ->condition('group_name', $group['group_name']); foreach ($query2->execute() as $field) { - $groups[$field['group_name']]['children'][] = $field; + $groups[$field['group_name'] . '-' . $field['type_name']]['children'][] = $field; } } } @@ -216,6 +217,8 @@ function field_group_update_7001() { 'default' => '', 'description' => 'The unique string identifier for a group.', )); + // Force drupal's schema to be rebuilt + drupal_get_schema('field_group', TRUE); module_load_include('module', 'field_group'); _field_group_recreate_identifiers(); @@ -227,6 +230,8 @@ function field_group_update_7001() { ->condition('name', 'field_group') ->execute(); + // Clear drupal and static cache. + field_group_info_groups(NULL, NULL, NULL, TRUE); } /** @@ -244,6 +249,8 @@ function field_group_update_7002() { // See http://drupal.org/node/1018550. _field_group_recreate_identifiers(); + // Clear drupal and static cache. + field_group_info_groups(NULL, NULL, NULL, TRUE); } /** @@ -255,4 +262,133 @@ function field_group_update_7003() { module_load_include('module', 'field_group'); _field_group_recreate_identifiers(); + // Clear drupal and static cache. + field_group_info_groups(NULL, NULL, NULL, TRUE); +} + +/** + * Update hook to make sure identifier is set as unique key. + */ +function field_group_update_7004() { + db_drop_unique_key('field_group', 'identifier'); + db_add_unique_key('field_group', 'identifier', array('identifier')); +} + +/** + * Checks all existing groups and removes optional HTML classes + * while adding them as extra classes. + */ +function field_group_update_7005() { + + // Migrate the field groups so they have a unique identifier. + $result = db_select('field_group', 'fg') + ->fields('fg') + ->execute(); + $rows = array(); + foreach($result as $row) { + //$row->identifier = $row->group_name . '|' . $row->entity_type . '|' . $row->bundle . '|' . $row->mode; + $row->data = unserialize($row->data); + $classes = explode(" ", $row->data['format_settings']['instance_settings']['classes']); + $optional_classes = array(str_replace("_", "-", $row->group_name), 'field-group-' . $row->data['format_type']); + foreach ($optional_classes as $optional_class) { + if (!in_array($optional_class, $classes)) { + $classes[] = $optional_class; + } + } + $row->data['format_settings']['instance_settings']['classes'] = implode(" ", $classes); + $rows[] = $row; + } + foreach ($rows as $row) { + drupal_write_record('field_group', $row, array('id')); + } + + // Clear drupal and static cache. + field_group_info_groups(NULL, NULL, NULL, TRUE); +} + +/** + * Save all optional HTML classes for fieldgroups located in features. + * If you need the optional classes, recreate feature after this update. + * If not, you can revert it. + */ +function field_group_update_7006() { + ctools_include("export"); + // Migrate the field groups so they have a unique identifier. + $field_groups = ctools_export_load_object("field_group"); + foreach ($field_groups as $row) { + + // Only update feature field_groups this time. + // Don't touch the fieldgroups in db. + + if ($row->export_type == EXPORT_IN_CODE) { + $classes = array(); + + if (isset($row->data['format_settings'], $row->data['format_settings']['instance_settings'], $row->data['format_settings']['instance_settings']['classes'])) { + $classes = explode(" ", $row->data['format_settings']['instance_settings']['classes']); + } + + $optional_classes = array(str_replace("_", "-", $row->group_name), 'field-group-' . $row->data['format_type']); + foreach ($optional_classes as $optional_class) { + if (!in_array($optional_class, $classes)) { + $classes[] = $optional_class; + } + } + $row->data['format_settings']['instance_settings']['classes'] = implode(" ", $classes); + unset($row->id); + drupal_write_record('field_group', $row); + } + } + + // Clear drupal and static cache. + field_group_info_groups(NULL, NULL, NULL, TRUE); +} + +/** + * Id attributes are now a setting. This update will insert the old id in the setting so old + * markup doesn't get broken. If you don't want an attribute, you can delete + * them on the fieldgroup settings. + */ +function field_group_update_7007() { + + ctools_include("export"); + + // Migrate the field groups so they have a unique identifier. + $field_groups = ctools_export_load_object("field_group"); + foreach ($field_groups as $row) { + // These field group types have an ID setting to populate. + $populate_id_setting = array( + 'html-element', + 'div', + 'html5', + 'fieldset', + 'tabs', + 'htabs', + 'accordion', + ); + + if (in_array($row->data['format_type'], $populate_id_setting)) { + // If mode is default, we don't know what view mode it was. Take full then. + $view_mode = $row->mode == 'default' ? 'full' : $row->mode; + $id = $row->entity_type . '_' . $row->bundle . '_' . $view_mode . '_' . $row->group_name; + $row->data['format_settings']['instance_settings']['id'] = $id; + } + + if ($row->export_type == EXPORT_IN_CODE) { + unset($row->id); + drupal_write_record('field_group', $row); + } + else { + drupal_write_record('field_group', $row, array('id')); + } + } + + // Clear drupal and static cache. + field_group_info_groups(NULL, NULL, NULL, TRUE); +} + +/** + * Clear cache to notice the CTools load callback. + */ +function field_group_update_7008() { + drupal_flush_all_caches(); } \ No newline at end of file diff --git a/sites/all/modules/contrib/fields/field_group/field_group.js b/sites/all/modules/contrib/fields/field_group/field_group.js old mode 100755 new mode 100644 index 72f3444..0b0a565 --- a/sites/all/modules/contrib/fields/field_group/field_group.js +++ b/sites/all/modules/contrib/fields/field_group/field_group.js @@ -41,9 +41,17 @@ Drupal.FieldGroup.Effects.processAccordion = { $('div.field-group-accordion-wrapper', context).once('fieldgroup-effects', function () { var wrapper = $(this); + // Get the index to set active. + var active_index = false; + wrapper.find('.accordion-item').each(function(i) { + if ($(this).hasClass('field-group-accordion-active')) { + active_index = i; + } + }); + wrapper.accordion({ - autoHeight: false, - active: '.field-group-accordion-active', + heightStyle: "content", + active: active_index, collapsible: true, changestart: function(event, ui) { if ($(this).hasClass('effect-none')) { @@ -56,17 +64,29 @@ Drupal.FieldGroup.Effects.processAccordion = { }); if (type == 'form') { + + var $firstErrorItem = false; + // Add required fields mark to any element containing required fields - wrapper.find('div.accordion-item').each(function(i){ + wrapper.find('div.field-group-accordion-item').each(function(i) { + if ($(this).is('.required-fields') && $(this).find('.form-required').length > 0) { - $('h3.ui-accordion-header').eq(i).append(' ').append($('.form-required').eq(0).clone()); + $('h3.ui-accordion-header a').eq(i).append(' ').append($('.form-required').eq(0).clone()); } if ($('.error', $(this)).length) { + // Save first error item, for focussing it. + if (!$firstErrorItem) { + $firstErrorItem = $(this).parent().accordion("activate" , i); + } $('h3.ui-accordion-header').eq(i).addClass('error'); - var activeOne = $(this).parent().accordion("activate" , i); - $('.ui-accordion-content-active', activeOne).css({height: 'auto', width: 'auto', display: 'block'}); } }); + + // Save first error item, for focussing it. + if (!$firstErrorItem) { + $('.ui-accordion-content-active', $firstErrorItem).css({height: 'auto', width: 'auto', display: 'block'}); + } + } }); } @@ -99,6 +119,9 @@ Drupal.FieldGroup.Effects.processHtabs = { Drupal.FieldGroup.Effects.processTabs = { execute: function (context, settings, type) { if (type == 'form') { + + var errorFocussed = false; + // Add required fields mark to any fieldsets containing required fields $('fieldset.vertical-tabs-pane', context).once('fieldgroup-effects', function(i) { if ($(this).is('.required-fields') && $(this).find('.form-required').length > 0) { @@ -106,8 +129,12 @@ Drupal.FieldGroup.Effects.processTabs = { } if ($('.error', $(this)).length) { $(this).data('verticalTab').link.parent().addClass('error'); - Drupal.FieldGroup.setGroupWithfocus($(this)); - $(this).data('verticalTab').focus(); + // Focus the first tab with error. + if (!errorFocussed) { + Drupal.FieldGroup.setGroupWithfocus($(this)); + $(this).data('verticalTab').focus(); + errorFocussed = true; + } } }); } @@ -171,6 +198,7 @@ Drupal.FieldGroup.Effects.processDiv = { */ Drupal.behaviors.fieldGroup = { attach: function (context, settings) { + settings.field_group = settings.field_group || Drupal.settings.field_group; if (settings.field_group == undefined) { return; } @@ -189,7 +217,24 @@ Drupal.behaviors.fieldGroup = { $('.fieldset-wrapper .fieldset > legend').css({display: 'block'}); $('.vertical-tabs fieldset.fieldset').addClass('default-fallback'); + // Add a new ID to each fieldset. + $('.group-wrapper .horizontal-tabs-panes > fieldset', context).once('group-wrapper-panes-processed', function() { + // Tats bad, but we have to keep the actual id to prevent layouts to break. + var fieldgroupID = 'field_group-' + $(this).attr('id'); + $(this).attr('id', fieldgroupID); + }); + // Set the hash in url to remember last userselection. + $('.group-wrapper ul li').once('group-wrapper-ul-processed', function() { + var fieldGroupNavigationListIndex = $(this).index(); + $(this).children('a').click(function() { + var fieldset = $('.group-wrapper fieldset').get(fieldGroupNavigationListIndex); + // Grab the first id, holding the wanted hashurl. + var hashUrl = $(fieldset).attr('id').replace(/^field_group-/, '').split(' ')[0]; + window.location.hash = hashUrl; + }); + }); + } }; -})(jQuery); \ No newline at end of file +})(jQuery); diff --git a/sites/all/modules/contrib/fields/field_group/field_group.module b/sites/all/modules/contrib/fields/field_group/field_group.module old mode 100755 new mode 100644 index 3254e51..4fe2547 --- a/sites/all/modules/contrib/fields/field_group/field_group.module +++ b/sites/all/modules/contrib/fields/field_group/field_group.module @@ -62,6 +62,16 @@ function field_group_menu() { 'type' => MENU_CALLBACK, 'file' => 'field_group.field_ui.inc', ) + $access; + + $items["$path/groups/%field_group_menu/enable"] = array( + 'load arguments' => array($entity_type, $bundle_arg, $bundle_pos, '%map'), + 'title' => 'Enable', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('field_group_enable_form', $group_position), + 'type' => MENU_CALLBACK, + 'file' => 'field_group.field_ui.inc', + ) + $access; + } } } @@ -107,7 +117,23 @@ function field_group_menu_load($group_name, $entity_type, $bundle_name, $bundle_ $args_pop = array_pop($args); $mode = array_pop($args_pop); - return field_group_load_field_group($group_name, $entity_type, $bundle_name, $mode); + $group = field_group_load_field_group($group_name, $entity_type, $bundle_name, $mode); + + return empty($group) ? FALSE : $group; +} + +/** + * Ctools load callback to load fieldgroup by identifier. + */ +function field_group_load_field_group_by_identifier($identifier) { + + $parts = explode('|', $identifier); + if (count($parts) != 4) { + return; + } + + return field_group_load_field_group($parts[0], $parts[1], $parts[2], $parts[3]); + } /** @@ -166,6 +192,45 @@ function field_group_theme() { ); } +/** + * Implements hook_theme_registry_alter(). + */ +function field_group_theme_registry_alter(&$theme_registry) { + + // Inject field_group_build_entity_groups in all entity theming functions. + $entity_info = entity_get_info(); + $entities = array(); + foreach ($entity_info as $entity => $info) { + if (isset($entity_info[$entity]['fieldable']) && $entity_info[$entity]['fieldable']) { + // User uses user_profile for theming. + if ($entity == 'user') $entity = 'user_profile'; + $entities[] = $entity; + } + } + + // Support for File Entity. + if (isset($theme_registry['file_entity'])) { + $entities[] = 'file_entity'; + } + + // Support for Entity API. + if (isset($theme_registry['entity'])) { + $entities[] = 'entity'; + } + + foreach ($entities as $entity) { + if (isset($theme_registry[$entity])) { + $theme_registry[$entity]['preprocess functions'][] = 'field_group_build_entity_groups'; + // DS support, make sure it comes after field_group. + if ($key = array_search('ds_entity_variables', $theme_registry[$entity]['preprocess functions'])) { + unset($theme_registry[$entity]['preprocess functions'][$key]); + $theme_registry[$entity]['preprocess functions'][] = 'ds_entity_variables'; + } + } + } + +} + /** * Implements hook_field_attach_delete_bundle(). * @@ -193,8 +258,8 @@ function field_group_field_attach_delete_bundle($entity_type, $bundle) { * Implements hook_field_attach_form(). */ function field_group_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) { - $form['#attached']['css'][] = drupal_get_path('module', 'field_group') . '/field_group.field_ui.css'; field_group_attach_groups($form, 'form', $form_state); + $form['#pre_render'][] = 'field_group_form_pre_render'; } /** @@ -236,24 +301,34 @@ function field_group_field_group_formatter_info() { return array( 'form' => array( + 'html-element' => array( + 'label' => t('HTML element'), + 'description' => t('This fieldgroup renders the inner content in a HTML element with classes and attributes.'), + 'instance_settings' => array('element' => 'div', 'show_label' => 0, 'label_element' => 'div', 'classes' => '', 'attributes' => '', 'required_fields' => 1, 'id' => ''), + ), 'div' => array( 'label' => t('Div'), - 'description' => t('This fieldgroup renders the inner content in a simple div with the titel as legend.'), + 'description' => t('This fieldgroup renders the inner content in a simple div with the title as legend.'), 'format_types' => array('open', 'collapsible', 'collapsed'), - 'instance_settings' => array('description' => '', 'show_label' => 1, 'label_element' => 'h3', 'effect' => 'none', 'speed' => 'fast', 'classes' => '', 'required_fields' => 1), + 'instance_settings' => array('description' => '', 'show_label' => 1, 'label_element' => 'h3', 'effect' => 'none', 'speed' => 'fast', 'classes' => '', 'required_fields' => 1, 'id' => ''), 'default_formatter' => 'open', ), + 'html5' => array( + 'label' => t('HTML5'), + 'description' => t('This fieldgroup renders the inner content in a semantic HTML5 wrapper'), + 'instance_settings' => array('wrapper' => '', 'classes' => '', 'id' => ''), + ), 'fieldset' => array( 'label' => t('Fieldset'), - 'description' => t('This fieldgroup renders the inner content in a fieldset with the titel as legend.'), + 'description' => t('This fieldgroup renders the inner content in a fieldset with the title as legend.'), 'format_types' => array('open', 'collapsible', 'collapsed'), - 'instance_settings' => array('description' => '', 'classes' => '', 'required_fields' => 1), + 'instance_settings' => array('description' => '', 'classes' => '', 'required_fields' => 1, 'id' => ''), 'default_formatter' => 'collapsible', ), 'tabs' => array( 'label' => t('Vertical tabs group'), 'description' => t('This fieldgroup renders child groups in its own vertical tabs wrapper.'), - 'instance_settings' => array('classes' => ''), + 'instance_settings' => array('classes' => '', 'id' => ''), ), 'tab' => array( 'label' => t('Vertical tab'), @@ -263,19 +338,19 @@ function field_group_field_group_formatter_info() { 'default_formatter' => 'closed', ), 'htabs' => array( - 'label' => ('Horizontal tabs group'), + 'label' => t('Horizontal tabs group'), 'description' => t('This fieldgroup renders child groups in its own horizontal tabs wrapper.'), - 'instance_settings' => array('classes' => ''), + 'instance_settings' => array('classes' => '', 'id' => ''), ), 'htab' => array( - 'label' => t('Horizontal tab item'), + 'label' => t('Horizontal tab'), 'format_types' => array('open', 'closed'), 'description' => t('This fieldgroup renders the content in a fieldset, part of horizontal tabs group.'), 'default_formatter' => 'closed', 'instance_settings' => array('description' => '', 'classes' => '', 'required_fields' => 1), ), 'multipage-group' => array( - 'label' => ('Multipage group'), + 'label' => t('Multipage group'), 'description' => t('This fieldgroup renders groups on separate pages.'), 'instance_settings' => array('classes' => '', 'page_header' => 3, 'move_additional' => 1, 'page_counter' => 1, 'move_button' => 0), ), @@ -289,7 +364,7 @@ function field_group_field_group_formatter_info() { 'accordion' => array( 'label' => t('Accordion group'), 'description' => t('This fieldgroup renders child groups as jQuery accordion.'), - 'instance_settings' => array('effect' => 'none', 'classes' => ''), + 'instance_settings' => array('effect' => 'none', 'classes' => '', 'id' => ''), ), 'accordion-item' => array( 'label' => t('Accordion item'), @@ -300,24 +375,34 @@ function field_group_field_group_formatter_info() { ), ), 'display' => array( + 'html-element' => array( + 'label' => t('HTML element'), + 'description' => t('This fieldgroup renders the inner content in a HTML element with classes and attributes.'), + 'instance_settings' => array('element' => 'div', 'show_label' => 0, 'label_element' => 'div', 'classes' => '', 'attributes' => '', 'required_fields' => 1, 'id' => ''), + ), 'div' => array( 'label' => t('Div'), - 'description' => t('This fieldgroup renders the inner content in a simple div with the titel as legend.'), + 'description' => t('This fieldgroup renders the inner content in a simple div with the title as legend.'), 'format_types' => array('open', 'collapsible', 'collapsed'), - 'instance_settings' => array('description' => '', 'show_label' => 1, 'label_element' => 'h3', 'effect' => 'none', 'speed' => 'fast', 'classes' => ''), + 'instance_settings' => array('description' => '', 'show_label' => 1, 'label_element' => 'h3', 'effect' => 'none', 'speed' => 'fast', 'classes' => '', 'id' => ''), 'default_formatter' => 'collapsible', ), + 'html5' => array( + 'label' => t('HTML5'), + 'description' => t('This fieldgroup renders the inner content in a semantic HTML5 wrapper'), + 'instance_settings' => array('wrapper' => '', 'classes' => '', 'id' => ''), + ), 'fieldset' => array( 'label' => t('Fieldset'), - 'description' => t('This fieldgroup renders the inner content in a fieldset with the titel as legend.'), + 'description' => t('This fieldgroup renders the inner content in a fieldset with the title as legend.'), 'format_types' => array('open', 'collapsible', 'collapsed'), - 'instance_settings' => array('description' => '', 'classes' => ''), + 'instance_settings' => array('description' => '', 'classes' => '', 'id' => ''), 'default_formatter' => 'collapsible', ), 'tabs' => array( 'label' => t('Vertical tabs group'), 'description' => t('This fieldgroup renders child groups in its own vertical tabs wrapper.'), - 'instance_settings' => array('classes' => ''), + 'instance_settings' => array('classes' => '', 'id' => ''), ), 'tab' => array( 'label' => t('Vertical tab'), @@ -327,21 +412,21 @@ function field_group_field_group_formatter_info() { 'default_formatter' => 'closed', ), 'htabs' => array( - 'label' => ('Horizontal tabs group'), + 'label' => t('Horizontal tabs group'), 'description' => t('This fieldgroup renders child groups in its own horizontal tabs wrapper.'), - 'instance_settings' => array('classes' => ''), + 'instance_settings' => array('classes' => '', 'id' => ''), ), 'htab' => array( 'label' => t('Horizontal tab item'), 'format_types' => array('open', 'closed'), 'description' => t('This fieldgroup renders the content in a fieldset, part of horizontal tabs group.'), - 'instance_settings' => array('description' => '', 'classes' => ''), + 'instance_settings' => array('description' => '', 'classes' => '', 'id' => ''), 'default_formatter' => 'closed', ), 'accordion' => array( 'label' => t('Accordion group'), 'description' => t('This fieldgroup renders child groups as jQuery accordion.'), - 'instance_settings' => array('description' => '', 'classes' => ''), + 'instance_settings' => array('description' => '', 'classes' => '', 'effect' => 'bounceslide', 'id' => ''), ), 'accordion-item' => array( 'label' => t('Accordion item'), @@ -380,28 +465,38 @@ function field_group_field_group_format_settings($group) { '#type' => 'select', '#options' => drupal_map_assoc($formatter['format_types']), '#default_value' => isset($group->format_settings['formatter']) ? $group->format_settings['formatter'] : $formatter['default_formatter'], - '#weight' => 1, + '#weight' => -4, ); } if (isset($formatter['instance_settings']['required_fields']) && $mode == 'form') { $form['instance_settings']['required_fields'] = array( '#type' => 'checkbox', - '#title' => t('Mark group for required fields.'), + '#title' => t('Mark group as required if it contains required fields.'), '#default_value' => isset($group->format_settings['instance_settings']['required_fields']) ? $group->format_settings['instance_settings']['required_fields'] : (isset($formatter['instance_settings']['required_fields']) ? $formatter['instance_settings']['required_fields'] : ''), '#weight' => 2, ); } + if (isset($formatter['instance_settings']['id'])) { + $form['instance_settings']['id'] = array( + '#title' => t('ID'), + '#type' => 'textfield', + '#default_value' => isset($group->format_settings['instance_settings']['id']) ? $group->format_settings['instance_settings']['id'] : (isset($formatter['instance_settings']['id']) ? $formatter['instance_settings']['id'] : ''), + '#weight' => 10, + '#element_validate' => array('field_group_validate_id'), + ); + } if (isset($formatter['instance_settings']['classes'])) { $form['instance_settings']['classes'] = array( '#title' => t('Extra CSS classes'), '#type' => 'textfield', '#default_value' => isset($group->format_settings['instance_settings']['classes']) ? $group->format_settings['instance_settings']['classes'] : (isset($formatter['instance_settings']['classes']) ? $formatter['instance_settings']['classes'] : ''), - '#weight' => 3, + '#weight' => 11, '#element_validate' => array('field_group_validate_css_class'), ); } + if (isset($formatter['instance_settings']['description'])) { $form['instance_settings']['description'] = array( '#title' => t('Description'), @@ -413,7 +508,40 @@ function field_group_field_group_format_settings($group) { // Add optional instance_settings. switch ($group->format_type) { + case 'html-element': + $form['instance_settings']['element'] = array( + '#title' => t('Element'), + '#type' => 'textfield', + '#default_value' => isset($group->format_settings['instance_settings']['element']) ? $group->format_settings['instance_settings']['element'] : $formatter['instance_settings']['element'], + '#description' => t('E.g. div, section, aside etc.'), + '#weight' => 1, + ); + + $form['instance_settings']['show_label'] = array( + '#title' => t('Show label'), + '#type' => 'select', + '#options' => array(0 => t('No'), 1 => t('Yes')), + '#default_value' => isset($group->format_settings['instance_settings']['show_label']) ? $group->format_settings['instance_settings']['show_label'] : $formatter['instance_settings']['show_label'], + '#weight' => 2, + ); + + $form['instance_settings']['label_element'] = array( + '#title' => t('Label element'), + '#type' => 'textfield', + '#default_value' => isset($group->format_settings['instance_settings']['label_element']) ? $group->format_settings['instance_settings']['label_element'] : $formatter['instance_settings']['label_element'], + '#weight' => 3, + ); + + $form['instance_settings']['attributes'] = array( + '#title' => t('Attributes'), + '#type' => 'textfield', + '#default_value' => isset($group->format_settings['instance_settings']['attributes']) ? $group->format_settings['instance_settings']['attributes'] : $formatter['instance_settings']['attributes'], + '#description' => t('E.g. name="anchor"'), + '#weight' => 4, + ); + break; case 'div': + $form['label']['#description'] = t('Please enter a label for collapsible elements'); $form['instance_settings']['show_label'] = array( '#title' => t('Show label'), '#type' => 'select', @@ -433,7 +561,7 @@ function field_group_field_group_format_settings($group) { '#type' => 'select', '#options' => array('none' => t('None'), 'blind' => t('Blind')), '#default_value' => isset($group->format_settings['instance_settings']['effect']) ? $group->format_settings['instance_settings']['effect'] : $formatter['instance_settings']['effect'], - '#weight' => 2, + '#weight' => 3, ); $form['instance_settings']['speed'] = array( '#title' => t('Speed'), @@ -443,7 +571,16 @@ function field_group_field_group_format_settings($group) { '#weight' => 3, ); break; + case 'html5': + $form['instance_settings']['wrapper'] = array( + '#title' => t('HTML5 wrapper'), + '#type' => 'select', + '#options' => array('section' => t('Section'), 'article' => t('Article'), 'header' => t('Header'), 'footer' => t('Footer'), 'aside' => t('Aside')), + '#default_value' => isset($group->format_settings['instance_settings']['wrapper']) ? $group->format_settings['instance_settings']['wrapper'] : 'section', + ); + break; case 'fieldset': + $form['label']['#description'] = t('Please enter a label for collapsible elements'); break; case 'multipage-group': $form['instance_settings']['page_header'] = array( @@ -505,34 +642,10 @@ function field_group_field_group_format_settings($group) { */ function field_group_pre_render_prepare(&$group) { - // Prepare extra classes. - $group->classes = array('field-group-' . $group->format_type, str_replace('_', '-', $group->group_name)); - if (isset($group->format_settings['formatter'])) { - $group->collapsible = in_array($group->format_settings['formatter'], array('collapsible', 'collapsed')); - // Open or closed horizontal or vertical tabs will be collapsible by default. - if ($group->format_type == 'tab' || $group->format_type == 'htab') { - $group->collapsible = TRUE; - } - $group->collapsed = in_array($group->format_settings['formatter'], array('collapsed', 'closed')); - if ($group->collapsible) { - $group->classes[] = 'collapsible'; - if ($group->collapsed) { - $group->classes[] = 'collapsed'; - } - } - } + $classes = _field_group_get_html_classes($group); - // Add a required-fields class to trigger the js. - if (isset($group->format_settings['instance_settings']) && !empty($group->format_settings['instance_settings']['required_fields'])) { - $group->classes[] = 'required-fields'; - } - - $group->classes = implode(' ', $group->classes); - if (isset($group->format_settings['instance_settings'], $group->format_settings['instance_settings']['classes'])) { - $group->classes .= ' ' . check_plain($group->format_settings['instance_settings']['classes']); - } - - $group->description = isset($group->format_settings['instance_settings']['description']) ? filter_xss_admin($group->format_settings['instance_settings']['description']) : ''; + $group->classes = implode(' ', $classes->required); + $group->description = !empty($group->format_settings['instance_settings']['description']) ? filter_xss_admin(t($group->format_settings['instance_settings']['description'])) : ''; } @@ -542,7 +655,7 @@ function field_group_pre_render_prepare(&$group) { * @param Array $elements by address. * @param Object $group The Field group info. */ -function field_group_field_group_pre_render(& $element, &$group, & $form) { +function field_group_field_group_pre_render(&$element, &$group, & $form) { field_group_pre_render_prepare($group); @@ -553,10 +666,11 @@ function field_group_field_group_pre_render(& $element, &$group, & $form) { 'data' => array('field_group' => array($group->format_type => $view_mode)), 'type' => 'setting', ); - $form['#attached']['js'][] = 'misc/form.js'; - $form['#attached']['js'][] = 'misc/collapse.js'; - $element['#id'] = $form['#entity_type'] . '_' . $form['#bundle'] . '_' . $view_mode . '_' . $group->group_name; + if (isset($group->format_settings['instance_settings']['id']) && !empty($group->format_settings['instance_settings']['id'])) { + $element['#id'] = drupal_html_id($group->format_settings['instance_settings']['id']); + } + $element['#weight'] = $group->weight; // Call the pre render function for the format type. @@ -586,9 +700,58 @@ function field_group_pre_render_fieldset(&$element, $group, &$form) { '#attributes' => array('class' => explode(' ', $group->classes)), '#description' => $group->description, ); - $element['#attached']['js'][] = 'misc/form.js'; - $element['#attached']['js'][] = 'misc/collapse.js'; + if ($group->collapsible || $group->collapsed) { + $element['#attached']['library'][] = array('system', 'drupal.collapse'); + } +} + +/** + * Implements field_group_pre_render_. + * Format type: HTML element. + * + * @param $element The field group form element. + * @param $group The Field group object prepared for pre_render. + * @param $form The root element or form. + */ +function field_group_pre_render_html_element(&$element, $group, &$form) { + $html_element = isset($group->format_settings['instance_settings']['element']) ? $group->format_settings['instance_settings']['element'] : 'div'; + $show_label = isset($group->format_settings['instance_settings']['show_label']) ? $group->format_settings['instance_settings']['show_label'] : 0; + $label_element = isset($group->format_settings['instance_settings']['label_element']) ? $group->format_settings['instance_settings']['label_element'] : 'div'; + $configured_attributes = isset($group->format_settings['instance_settings']['attributes']) ? ' ' . $group->format_settings['instance_settings']['attributes'] : ''; + $group->classes = trim($group->classes); + + // This regex split the attributes string so that we can pass that + // later to drupal_attributes(). + preg_match_all('/([^\s=]+)="([^"]+)"/', $configured_attributes, $matches); + + $element_attributes = array(); + // Put the attribute and the value together. + foreach ($matches[1] as $key => $attribute) { + $element_attributes[$attribute] = $matches[2][$key]; + } + + // Add the classes to the attributes array. + if (!isset($element_attributes['class']) && $group->classes) { + $element_attributes['class'] = $group->classes; + } + elseif (isset($element_attributes['class']) && $group->classes) { + $element_attributes['class'] .= ' ' . $group->classes; + } + + if (isset($element['#id'])) { + $element_attributes['id'] = $element['#id']; + } + + // Sanitize the attributes. + $element_attributes = _filter_xss_attributes(drupal_attributes($element_attributes)); + $attributes = $element_attributes ? ' ' . implode(' ', $element_attributes) : ''; + + $element['#prefix'] = '<' . $html_element . $attributes . '>'; + if ($show_label) { + $element['#prefix'] .= '<' . $label_element . '>' . check_plain(t($group->label)) . ''; + } + $element['#suffix'] = ''; } /** @@ -604,19 +767,21 @@ function field_group_pre_render_div(&$element, $group, &$form) { $show_label = isset($group->format_settings['instance_settings']['show_label']) ? $group->format_settings['instance_settings']['show_label'] : 0; $label_element = isset($group->format_settings['instance_settings']['label_element']) ? $group->format_settings['instance_settings']['label_element'] : 'h2'; $effect = isset($group->format_settings['instance_settings']['effect']) ? $group->format_settings['instance_settings']['effect'] : 'none'; - $speed = isset($group->format_settings['instance_settings']['speed']) ? $group->format_settings['instance_settings']['speed'] : 'none'; $element['#type'] = 'markup'; + $id = isset($element['#id']) ? ' id="' . $element['#id'] . '"' : ''; - $group->classes .= " speed-$speed effect-$effect"; if ($group->format_settings['formatter'] != 'open') { - $element['#prefix'] = '
    + + $element['#prefix'] = ' <' . $label_element . '>' . check_plain(t($group->label)) . ' -
    '; +
    '; $element['#suffix'] = '
    '; } else { - $element['#prefix'] = '
    '; + $class_attribute = !empty($group->classes) ? ' class="' . $group->classes . '"' : ''; + + $element['#prefix'] = ''; if ($show_label) { $element['#prefix'] .= '<' . $label_element . '>' . check_plain(t($group->label)) . ''; } @@ -627,11 +792,29 @@ function field_group_pre_render_div(&$element, $group, &$form) { } if ($effect == 'blind') { - drupal_add_library('system', 'effects.blind'); + $element['#attached']['library'][] = array('system', 'effects.blind'); } } +/** + * Implements field_group_pre_render_. + * Format type: HTML5. + * + * @param $element The field group form element. + * @param $group The Field group object prepared for pre_render. + * @param $form The root element or form. + */ +function field_group_pre_render_html5(&$element, $group, &$form) { + $id = !empty($element['#id']) ? ' id="' . $element['#id'] . '"' : ''; + $class = !empty($group->classes) ? ' class="' . $group->classes . '"' : ''; + $element += array( + '#type' => 'markup', + '#prefix' => '<' . $group->format_settings['instance_settings']['wrapper'] . $id . $class . '>', + '#suffix' => 'format_settings['instance_settings']['wrapper'] . '>', + ); +} + /** * Implements field_group_pre_render_. * Format type: Accordion. @@ -641,13 +824,15 @@ function field_group_pre_render_div(&$element, $group, &$form) { * @param $form The root element or form. */ function field_group_pre_render_accordion(&$element, $group, &$form) { - $effect = isset($group->format_settings['instance_settings']['effect']) ? $group->format_settings['instance_settings']['effect'] : 'none'; - $group->classes .= " effect-$effect"; + // Add the jQuery UI accordion. + $element['#attached']['library'][] = array('system', 'ui.accordion'); + + $id = !empty($element['#id']) ? ' id="' . $element['#id'] . '"' : ''; $element += array( '#type' => 'markup', - '#prefix' => '
    ', + '#prefix' => '
    ', '#suffix' => '
    ', ); } @@ -664,7 +849,7 @@ function field_group_pre_render_accordion_item(&$element, $group, &$form) { $element += array( '#type' => 'markup', - '#prefix' => '

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

    + '#prefix' => '

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

    ', '#suffix' => '
    ', //'#attributes' => array('class' => array($group->format_type)), @@ -672,7 +857,6 @@ function field_group_pre_render_accordion_item(&$element, $group, &$form) { if (!empty($group->description)) { $element['#prefix'] .= '
    ' . $group->description . '
    '; } - drupal_add_library('system', 'ui.accordion'); } @@ -686,14 +870,32 @@ function field_group_pre_render_accordion_item(&$element, $group, &$form) { */ function field_group_pre_render_htabs(&$element, $group, &$form) { + $classes = 'field-group-' . $group->format_type . '-wrapper'; + if (!empty($group->classes)) { + $classes .= ' ' . $group->classes; + } + + $id = !empty($element['#id']) ? ' id="' . $element['#id'] . '"' : ''; + $element += array( '#type' => 'horizontal_tabs', '#title' => check_plain(t($group->label)), '#theme_wrappers' => array('horizontal_tabs'), - '#prefix' => '
    ', + '#prefix' => '
    ', '#suffix' => '
    ', ); + // By default vertical_tabs don't have titles but you can override it in the theme. + if (!empty($group->label)) { + $element['#title'] = check_plain($group->label); + } + + // Only add form.js on forms. + if (!empty($form['#type']) && $form['#type'] == 'form') { + $element['#attached']['js'][] = 'misc/form.js'; + } + + $element['#attached']['library'][] = array('field_group', 'horizontal-tabs'); } /** @@ -776,6 +978,7 @@ function field_group_pre_render_multipage(&$element, $group, &$form) { '#description' => $group->description, ); + $element['#attached']['library'][] = array('field_group', 'multipage'); } /** @@ -788,20 +991,32 @@ function field_group_pre_render_multipage(&$element, $group, &$form) { */ function field_group_pre_render_tabs(&$element, $group, &$form) { + $classes = 'field-group-' . $group->format_type . '-wrapper'; + if (!empty($group->classes)) { + $classes .= ' ' . $group->classes; + } + + $id = !empty($element['#id']) ? ' id="' . $element['#id'] . '"' : ''; + $element += array( '#type' => 'vertical_tabs', '#theme_wrappers' => array('vertical_tabs'), - '#prefix' => '
    ', + '#prefix' => '
    ', '#suffix' => '
    ', ); + + // By default vertical_tabs don't have titles but you can override it in the theme. + if (!empty($group->label)) { + $element['#title'] = check_plain($group->label); + } + $element[$group->group_name . '__active_tab'] = array( '#type' => 'hidden', '#default_value' => '', '#attributes' => array('class' => array('vertical-tabs-active-tab')), ); - $form['#attached']['library'][] = array('system', 'drupal.form'); - $form['#attached']['library'][] = array('system', 'drupal.collapse'); + $element['#attached']['library'][] = array('system', 'drupal.collapse'); } /** @@ -876,8 +1091,7 @@ function field_group_pre_render_tab(&$element, $group, &$form) { '#prefix' => '
    ', '#suffix' => '
    ', ); - $form['#attached']['js'][] = 'misc/form.js'; - $form['#attached']['js'][] = 'misc/collapse.js'; + $form['#attached']['library'][] = array('system', 'drupal.collapse'); } $form['additional_settings'][$group->group_name] = $add; // Nest the fields inside the appropriate structure. @@ -894,7 +1108,7 @@ function field_group_pre_render_tab(&$element, $group, &$form) { * Implements hook_field_group_build_pre_render_alter(). * @param Array $elements by address. */ -function field_group_field_group_build_pre_render_alter(& $element) { +function field_group_field_group_build_pre_render_alter(&$element) { // Someone is doing a node view, in a node view. Reset content. // TODO Check if this breaks something else. @@ -916,7 +1130,6 @@ function field_group_field_group_build_pre_render_alter(& $element) { // Add the default field_group javascript and stylesheet. $element['#attached']['js'][] = drupal_get_path('module', 'field_group') . '/field_group.js'; - $element['#attached']['css'][] = drupal_get_path('module', 'field_group') . '/field_group.css'; // Move additional settings to the last multipage pane if configured that way. // Note that multipages MUST be in the root of the form. @@ -947,18 +1160,17 @@ function field_group_field_group_build_pre_render_alter(& $element) { */ function field_group_remove_empty_form_groups($name, & $element, $groups, &$form_groups, $entity) { - $exceptions = array('user__account', 'comment__author'); - $children = element_children($element); $hasChildren = FALSE; if (count($children)) { foreach ($children as $childname) { - if (in_array($childname, $groups)) { + + if (in_array($childname, $groups, TRUE)) { field_group_remove_empty_form_groups($childname, $element[$childname], $groups, $form_groups, $entity); } - $exception = $entity . '__' . $childname; - $hasChildren = $hasChildren ? TRUE : (isset($element[$childname]['#type']) || in_array($exception, $exceptions)); + $hasChildren = $hasChildren ? TRUE : _field_group_is_empty_element($element, $entity, $childname, $groups); + } } @@ -982,6 +1194,43 @@ function field_group_remove_empty_form_groups($name, & $element, $groups, &$form } +/** + * Determine if an element has non-empty children. + */ +function _field_group_is_empty_element($element, $entity, $childname, $groups) { + + $exceptions = array('user__account', 'comment__author'); + $exception = $entity . '__' . $childname; + + if (in_array($exception, $exceptions)) { + return TRUE; + } + + if (isset($element[$childname]['#type']) + || isset($element[$childname]['#markup']) + || isset($element[$childname]['#prefix']) + || isset($element[$childname]['#suffix']) + ) { + return TRUE; + } + + // Prevent a double recursive loop (groups are already recursive looped in field_group_remove_empty_form_groups. + if (in_array($childname, $groups)) { + return FALSE; + } + + $children = element_children($element[$childname]); + + foreach ($children as $child) { + if (_field_group_is_empty_element($element[$childname], $entity, $child, $groups)) { + return TRUE; + } + } + + return FALSE; + +} + /** * Remove empty groups on entity display. * @param array $element @@ -1004,8 +1253,8 @@ function field_group_remove_empty_display_groups(& $element, $groups) { $empty_group = FALSE; } } - // Child is a field, the element is not empty. - elseif (!empty($element[$name])) { + // Child is a field, the element is not empty and access is set to true (or empty). + elseif (!empty($element[$name]) && (!isset($element[$name]['#access']) || $element[$name]['#access'])) { $empty_group = FALSE; } @@ -1042,7 +1291,12 @@ function field_group_field_group_format_summary($group) { $output .= '' . $key . ' '; if (isset($group_form['instance_settings'], $group_form['instance_settings'][$key]['#options'])) { - $value = $group_form['instance_settings'][$key]['#options'][$value]; + if (is_array($value)) { + $value = implode(array_filter($value), ', '); + } + else { + $value = $group_form['instance_settings'][$key]['#options'][$value]; + } } // Shorten the string. @@ -1253,7 +1507,7 @@ function theme_horizontal_tabs($variables) { * An associative array containing the properties and children of the * fieldset. * @param $form_state - * The $form_state array for the form this horizontal tab widget belongs to. + * The $form_state array for the form this multipage tab widget belongs to. * @return * The processed element. */ @@ -1296,7 +1550,7 @@ function form_process_multipage($element, &$form_state) { function theme_multipage($variables) { $element = $variables['element']; // Add required JavaScript and Stylesheet. - drupal_add_library('field_group', 'multipage'); + $element['#attached']['library'][] = array('field_group', 'multipage'); $output = '

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

    '; @@ -1402,6 +1656,10 @@ function field_group_info_groups($entity_type = NULL, $bundle = NULL, $view_mode $groups = $cached->data; } else { + $ctools_export_load_object = &drupal_static('ctools_export_load_object'); + $ctools_export_load_object_all = &drupal_static('ctools_export_load_object_all'); + unset($ctools_export_load_object['field_group']); + unset($ctools_export_load_object_all['field_group']); $groups = field_group_read_groups(); cache_set('field_groups', $groups, 'cache_field'); } @@ -1410,45 +1668,60 @@ function field_group_info_groups($entity_type = NULL, $bundle = NULL, $view_mode if (!isset($entity_type)) { return $groups; } - else { - if (isset($groups[$entity_type][$bundle][$view_mode])) { - return $groups[$entity_type][$bundle][$view_mode]; - } - else { - return array(); - } + elseif (!isset($bundle) && isset($groups[$entity_type])) { + return $groups[$entity_type]; } + elseif (!isset($view_mode) && isset($groups[$entity_type][$bundle])) { + return $groups[$entity_type][$bundle]; + } + elseif (isset($groups[$entity_type][$bundle][$view_mode])) { + return $groups[$entity_type][$bundle][$view_mode]; + } + return array(); } /** * Read all groups. * - * @param $params - * Parameters for the query - * $name The name of the entity. - * $bundle The name of the bundle. - * $view_mode The view mode. + * @param array $conditions + * Parameters for the query, as elements of the $conditions array. + * 'entity_type' The name of the entity type. + * 'bundle' The name of the bundle. + * 'mode' The view mode. + * + * @param boolean $enabled + * Return enabled or disabled groups. + * + * @return array + * Array of groups. */ -function field_group_read_groups($params = array()) { +function field_group_read_groups($conditions = array(), $enabled = TRUE) { $groups = array(); ctools_include('export'); - if (empty($params)) { + if (empty($conditions)) { $records = ctools_export_load_object('field_group'); } else { - $records = ctools_export_load_object('field_group', 'conditions', $params); + $records = ctools_export_load_object('field_group', 'conditions', $conditions); } foreach ($records as $group) { - // Deleted groups. - // @TODO remove the code below and add something meaningfull for "disabled" state. - if (isset($group->disabled) && $group->disabled) { + + // Return only enabled groups. + if ($enabled && isset($group->disabled) && $group->disabled) { continue; } + // Return only disabled groups. + elseif (!$enabled && (!isset($group->disabled) || !$group->disabled)) { + continue; + } + $groups[$group->entity_type][$group->bundle][$group->mode][$group->group_name] = field_group_unpack($group); + } + drupal_alter('field_group_info', $groups); return $groups; } @@ -1493,28 +1766,29 @@ function field_group_exists($group_name, $entity_type, $bundle, $mode) { /** * Unpacks a database row in a FieldGroup object. - * @param $group + * @param $packed_group * Database result object with stored group data. * @return $group * Field group object. */ -function field_group_unpack($group) { +function field_group_unpack($packed_group) { + if (empty($packed_group->data)) { + return $packed_group; + } // Extract unserialized data. - if (isset($group->data)) { - $data = $group->data; - unset($group->data); - $group->label = $data['label']; - $group->weight = $data['weight']; - $group->children = $data['children']; - $group->format_type = !empty($data['format_type']) ? $data['format_type'] : 'fieldset'; - if (isset($data['format_settings'])) { - $group->format_settings = $data['format_settings']; - } + $group = clone $packed_group; + $data = $group->data; + unset($group->data); + $group->label = isset($data['label']) ? $data['label'] : ''; + $group->weight = isset($data['weight']) ? $data['weight'] : ''; + $group->children = isset($data['children']) ? $data['children'] : ''; + $group->format_type = !empty($data['format_type']) ? $data['format_type'] : 'fieldset'; + if (isset($data['format_settings'])) { + $group->format_settings = $data['format_settings']; } return $group; - } /** @@ -1600,6 +1874,7 @@ function field_group_group_save(& $group) { $object->export_type = EXPORT_IN_DATABASE; module_invoke_all('field_group_create_field_group', $object); } + return drupal_write_record('field_group', $object, $update); } @@ -1617,7 +1892,7 @@ function field_group_formatter_info($display_overview = FALSE) { $formatters = array(); $formatters += module_invoke_all('field_group_formatter_info'); $hidden_region = array( - 'label' => t(''), + 'label' => '<' . t('Hidden') . '>', 'description' => '', 'format_types' => array(), 'instance_settings' => array(), @@ -1653,51 +1928,79 @@ function field_group_attach_groups(&$element, $view_mode, $form_state = array()) // Create a lookup array. $group_children = array(); foreach ($element['#groups'] as $group_name => $group) { - foreach ($group->children as $child) { - $group_children[$child] = $group_name; + if (!empty($group->children)) { + foreach ($group->children as $child) { + $group_children[$child] = $group_name; + } } } $element['#group_children'] = $group_children; - // Add a pre render callback. - // This is needed since process seems too early for the front view modes. - // This is main bottleneck in field_group. By using this, we can't process - // our added groups in a normal way, resulting in early creation of vertical - // tabs, horizontal tabs and multipages. - $element['#pre_render'][] = 'field_group_build_pre_render'; - } /** - * Process/ Pre-render callback. + * Pre render callback for rendering groups. + * @see field_group_field_attach_form + * @param $element Form that is being rendered. + */ +function field_group_form_pre_render(&$element) { + return field_group_build_entity_groups($element, 'form'); +} + +/** + * Preprocess/ Pre-render callback. * - * Depending on whether it is a form build or content build. - * Form api go through more than a regular build. #process is - * needed here, where #pre_render is ideal for the regular array. - * @see field_group_attach_groups() + * @see field_group_form_pre_render() + * @see field_group_theme_registry_alter * @see field_group_fields_nest() - * @param $element Form element + * @param $vars preprocess vars or form element + * @param $type The type of object being rendered * @return $element Array with re-arranged fields in forms. */ -function field_group_build_pre_render($element) { +function field_group_build_entity_groups(&$vars, $type) { - // Skip the nesting and field_group functions if no fieldgroups. - // This could be because you don't see them in the UI or programmatically. + if ($type == 'form') { + $element = &$vars; + $nest_vars = NULL; + } + else { + $element = &$vars['elements']; + $nest_vars = &$vars; + } + + // No groups on the entity. if (empty($element['#fieldgroups'])) { return $element; } - // Merge our #fieldgroups with #groups to avoid conflicts on fieldset types. - $element['#groups'] = array_merge($element['#groups'], $element['#fieldgroups']); - // Nest the fields in the corresponding field groups. - field_group_fields_nest($element); + field_group_fields_nest($element, $nest_vars); // Allow others to alter the pre_rendered build. drupal_alter('field_group_build_pre_render', $element); - return $element; + // Return the element on forms. + if ($type == 'form') { + return $element; + } + // No groups on the entity. Prerender removed empty field groups. + if (empty($element['#fieldgroups'])) { + return $element; + } + + // Put groups inside content if we are rendering an entity_view. + foreach ($element['#fieldgroups'] as $group) { + if (!empty($element[$group->group_name]) && $type != 'user_profile') { + $vars['content'][$group->group_name] = $element[$group->group_name]; + } + elseif (!empty($element[$group->group_name])) { + $vars['user_profile'][$group->group_name] = $element[$group->group_name]; + } + } + + // New css / js can be attached. + drupal_process_attached($element); } /** @@ -1709,40 +2012,128 @@ function field_group_build_pre_render($element) { * stash fields and groups in it while we go deeper in the array. * @param Array $element * The current element to analyse for grouping. + * @param Array $vars + * Rendering vars from the entity being viewed. */ -function field_group_fields_nest(&$element) { +function field_group_fields_nest(&$element, &$vars = NULL) { // Create all groups and keep a flat list of references to these groups. $group_references = array(); - foreach ($element['#groups'] as $group_name => $group) { - $element[$group_name] = array(); - $group_references[$group_name] = &$element[$group_name]; + foreach ($element['#fieldgroups'] as $group_name => $group) { + // check for any erroneous groups from other modules + if (is_string($group_name)) { + // Construct own weight, as some fields (for example preprocess fields) don't have weight set. + $element[$group_name] = array(); + $group_references[$group_name] = &$element[$group_name]; + // Get group parents + $parents = array(); + $current_group = $group; + while (!empty($current_group)) { + array_unshift($parents, $current_group->group_name); + $current_group = isset($element['#fieldgroups'][$current_group->parent_name]) ? + $element['#fieldgroups'][$current_group->parent_name] : NULL; + } + $group_references[$group_name]['#array_parents'] = $parents; + $element['#fieldgroups'][$group_name]->array_parents = $parents; + // Remove self from parents and set #field_parents + array_pop($parents); + $group_references[$group_name]['#field_parents'] = $parents; + } } + // Loop through all form children looking for those that are supposed to be + // in groups, and insert placeholder element for the new group field in the + // correct location within the form structure. + $element_clone = array(); + foreach (element_children($element) as $child_name) { + $element_clone[$child_name] = $element[$child_name]; + // If this element is in a group, create the placeholder element. + if (isset($element['#group_children'][$child_name])) { + $element_clone[$element['#group_children'][$child_name]] = array(); + } + } + $element = array_merge($element_clone, $element); + // Move all children to their parents. Use the flat list of references for // direct access as we don't know where in the root_element hierarchy the // parent currently is situated. foreach ($element['#group_children'] as $child_name => $parent_name) { - // Block denied fields (#access) before they are put in groups. - // Fields (not groups) that don't have children (like field_permissions) are removed - // in field_group_field_group_build_pre_render_alter. - if (isset($element[$child_name]) && (!isset($element[$child_name]['#access']) || $element[$child_name]['#access'])) { + + // Entity being viewed + if ($vars) { + // If not a group, check vars['content'] for empty field. + if (!isset($element['#fieldgroups'][$child_name]) && isset($vars['content'][$child_name])) { + $group_references[$parent_name][$child_name] = $vars['content'][$child_name]; + unset($vars['content'][$child_name]); + } + elseif (!isset($element['#fieldgroups'][$child_name]) && isset($vars['user_profile'][$child_name])) { + $group_references[$parent_name][$child_name] = $vars['user_profile'][$child_name]; + unset($vars['user_profile'][$child_name]); + } // If this is a group, we have to use a reference to keep the reference // list intact (but if it is a field we don't mind). - $group_references[$parent_name][$child_name] = &$element[$child_name]; + else { + $group_references[$parent_name][$child_name] = &$element[$child_name]; + unset($element[$child_name]); + } } - // The child has been copied to its parent: remove it from the root element. - unset($element[$child_name]); + // Form being viewed + else { + + // Block denied fields (#access) before they are put in groups. + // Fields (not groups) that don't have children (like field_permissions) are removed + // in field_group_field_group_build_pre_render_alter. + if (isset($element[$child_name]) && (!isset($element[$child_name]['#access']) || $element[$child_name]['#access'])) { + // If this is a group, we have to use a reference to keep the reference + // list intact (but if it is a field we don't mind). + $group_references[$parent_name][$child_name] = &$element[$child_name]; + $group_references[$parent_name]['#weight'] = $element['#fieldgroups'][$parent_name]->weight; + // Prepend #array_parents & #field_parents of group child element & its element_children + // if those keys are set, and don't already include the group parents + $group_child = &$group_references[$parent_name][$child_name]; + $group_parents = $group_references[$parent_name]['#array_parents']; + $process_elements = array_merge(array(&$group_child), _field_group_element_children_recursive_ref($group_child)); + foreach ($process_elements as $key => $current_element) { + if (isset($current_element['#array_parents']) && !in_array($group_parents[0], $current_element['#array_parents'])) { + $process_elements[$key]['#array_parents'] = array_merge($group_parents, $current_element['#array_parents']); + } + if (isset($current_element['#field_parents']) && !in_array($group_parents[0], $current_element['#field_parents'])) { + $process_elements[$key]['#field_parents'] = array_merge($group_parents, $current_element['#field_parents']); + } + } + } + + // The child has been copied to its parent: remove it from the root element. + unset($element[$child_name]); + } + } // Bring extra element wrappers to achieve a grouping of fields. // This will mainly be prefix and suffix altering. - foreach ($element['#groups'] as $group_name => $group) { + foreach ($element['#fieldgroups'] as $group_name => $group) { field_group_pre_render($group_references[$group_name], $group, $element); } } +/** + * Recursive element_children, returns children by reference + */ +function _field_group_element_children_recursive_ref(&$element) { + $results = array(); + $children = element_children($element); + foreach ($children as $key) { + $child = &$element[$key]; + if (is_array($child)) { + $results[] = &$child; + $results = array_merge($results, _field_group_element_children_recursive_ref($child)); + } + unset($child); + } + return $results; +} + /** * Function to pre render the field group element. * @@ -1776,3 +2167,152 @@ function field_group_pre_render(& $element, $group, & $form) { drupal_alter('field_group_pre_render', $element, $group, $form); } + +/** + * Hides field groups including children in a render array. + * + * @param array $element + * A render array. Can be a form, node, user, ... + * @param array $group_names + * An array of field group names that should be hidden. + */ +function field_group_hide_field_groups(&$element, $group_names) { + foreach ($group_names as $group_name) { + if (isset($element['#fieldgroups'][$group_name]) && isset($element['#group_children'])) { + // Hide the field group. + $element['#fieldgroups'][$group_name]->format_type = 'hidden'; + // Hide the elements inside the field group. + $sub_groups = array(); + foreach (array_keys($element['#group_children'], $group_name) as $field_name) { + if (isset($element['#fieldgroups'][$field_name])) { + $sub_groups[] = $field_name; + } else { + $element[$field_name]['#access'] = FALSE; + } + } + field_group_hide_field_groups($element, $sub_groups); + } + } +} + +/** + * Calculates html classes for a group. + */ +function _field_group_get_html_classes(&$group) { + + if (isset($group->format_settings['formatter'])) { + $group->collapsible = in_array($group->format_settings['formatter'], array('collapsible', 'collapsed')); + // Open or closed horizontal or vertical tabs will be collapsible by default. + if ($group->format_type == 'tab' || $group->format_type == 'htab') { + $group->collapsible = TRUE; + } + $group->collapsed = in_array($group->format_settings['formatter'], array('collapsed', 'closed')); + } + + $classes = new stdClass(); + + // Prepare extra classes, required and optional ones. + $optional = array(str_replace('_', '-', $group->group_name)); + $required = array(); + if ($group->format_type == 'multipage') { + $required[] = 'field-group-' . $group->format_type; + } + else { + $optional[] = 'field-group-' . $group->format_type; + } + + if (isset($group->format_settings['formatter']) && $group->collapsible) { + $required[] = 'collapsible'; + if ($group->collapsed) { + $required[] = 'collapsed'; + } + } + + if (isset($group->format_settings['instance_settings'])) { + + // Add a required-fields class to trigger the js. + if (!empty($group->format_settings['instance_settings']['required_fields'])) { + $required[] = 'required-fields'; + } + + // Add user selected classes. + if (!empty($group->format_settings['instance_settings']['classes'])) { + $required[] = check_plain($group->format_settings['instance_settings']['classes']); + } + + // Extra required classes for div. + if ($group->format_type == 'div') { + if ($group->format_settings['formatter'] != 'open') { + + $speed = isset($group->format_settings['instance_settings']['speed']) ? $group->format_settings['instance_settings']['speed'] : 'none'; + $required[] = 'speed-' . $speed; + + $effect = isset($group->format_settings['instance_settings']['effect']) ? $group->format_settings['instance_settings']['effect'] : 'none'; + $required[] = 'effect-' . $effect; + } + } + + // Extra required classes for accordions. + elseif ($group->format_type == 'accordion') { + $required[] = 'field-group-' . $group->format_type . '-wrapper'; + $effect = isset($group->format_settings['instance_settings']['effect']) ? $group->format_settings['instance_settings']['effect'] : 'none'; + $required[] = 'effect-' . $effect; + } + + } + + $classes->required = $required; + $classes->optional = $optional; + + return $classes; +} + +/** + * Get the default formatter settings for a given formatter and a mode. + */ +function _field_group_get_default_formatter_settings($format_type, $mode) { + + $field_group_types = field_group_formatter_info(); + $display_mode = $mode == 'form' ? 'form' : 'display'; + $formatter = $field_group_types[$display_mode][$format_type]; + + return array( + 'formatter' => isset($formatter['default_formatter']) ? $formatter['default_formatter'] : '', + 'instance_settings' => $formatter['instance_settings'] + ); +} + +/** + * Callback to bulk export field groups. + */ +function field_group_field_group_to_hook_code($data, $module) { + ctools_include('export'); + $schema = ctools_export_get_schema('field_group'); + $export = $schema['export']; + + $translatables = array(); + $objects = ctools_export_load_object('field_group', 'names', array_values($data)); + $code = "/**\n"; + $code .= " * Implements hook_{$export['default hook']}()\n"; + $code .= " */\n"; + $code .= "function " . $module . "_{$export['default hook']}() {\n"; + $code .= " \${$export['identifier']}s = array();\n\n"; + foreach ($objects as $object) { + $code .= ctools_export_object('field_group', $object, ' '); + $code .= " \${$export['identifier']}s['" . check_plain($object->{$export['key']}) . "'] = \${$export['identifier']};\n\n"; + if (!empty($object->data['label'])) { + $translatables[] = $object->data['label']; + } + if (!empty($object->data['description'])) { + $translatables[] = $object->data['description']; + } + } + + if (!empty($translatables)) { + $code .= features_translatables_export($translatables, ' ') . "\n"; + } + + $code .= " return \${$export['identifier']}s;"; + $code .= "}\n"; + return $code; +} diff --git a/sites/all/modules/contrib/fields/field_group/horizontal-tabs/horizontal-tabs-rtl.css b/sites/all/modules/contrib/fields/field_group/horizontal-tabs/horizontal-tabs-rtl.css new file mode 100644 index 0000000..e579eea --- /dev/null +++ b/sites/all/modules/contrib/fields/field_group/horizontal-tabs/horizontal-tabs-rtl.css @@ -0,0 +1,15 @@ +div.horizontal-tabs { + margin: 0 0 1em 0; +} + +.horizontal-tabs ul.horizontal-tabs-list { + border-right: 0; + border-left: 1px solid #dedede; +} + +/* Layout of each tab */ +.horizontal-tabs ul.horizontal-tabs-list li { + border-right: 0; + border-left: 1px solid #ccc; + float: right; +} diff --git a/sites/all/modules/contrib/fields/field_group/horizontal-tabs/horizontal-tabs.css b/sites/all/modules/contrib/fields/field_group/horizontal-tabs/horizontal-tabs.css old mode 100755 new mode 100644 index a8d445f..86a431f --- a/sites/all/modules/contrib/fields/field_group/horizontal-tabs/horizontal-tabs.css +++ b/sites/all/modules/contrib/fields/field_group/horizontal-tabs/horizontal-tabs.css @@ -1,5 +1,3 @@ -@CHARSET "UTF-8"; - div.horizontal-tabs { margin: 0 0 1em 0; /* LTR */ padding: 0; @@ -16,7 +14,7 @@ div.horizontal-tabs { list-style: none; list-style-image: none; /* IE6 */ background-color: #dedede; - border-right: 1px solid #dedede; + border-right: 1px solid #dedede; /* LTR */ width: 100%; height: auto; clear: both; @@ -27,19 +25,20 @@ div.horizontal-tabs { border: 0; } -.horizontal-tabs-pane>legend { +fieldset.horizontal-tabs-pane > legend, +fieldset.vertical-tabs-pane fieldset.horizontal-tabs-pane > legend { display: none; } /* Layout of each tab */ .horizontal-tabs ul.horizontal-tabs-list li { background: #eee; - border-right: 1px solid #ccc; + border-right: 1px solid #ccc; /* LTR */ padding: 1px; - padding-top: 0; + padding-top: 0; margin: 0; min-width: 5em; /* IE7 */ - float: left; + float: left; /* LTR */ } .horizontal-tabs ul.horizontal-tabs-list li.selected { background-color: #fff; @@ -58,6 +57,9 @@ div.horizontal-tabs { .horizontal-tabs ul.horizontal-tabs-list li:focus { background-color: #ddd; } +.horizontal-tabs ul.horizontal-tabs-list :focus { + outline: none; +} .horizontal-tabs ul.horizontal-tabs-list li a:focus strong, .horizontal-tabs ul.horizontal-tabs-list li a:active strong, .horizontal-tabs ul.horizontal-tabs-list li a:hover strong { @@ -70,7 +72,7 @@ div.horizontal-tabs { text-decoration: none; padding: 0.5em 0.6em 0.3em 0.6em; position:relative; - top: 0px; + top: 0px; } .horizontal-tabs ul.horizontal-tabs-list .selected strong { color: #000; @@ -84,9 +86,9 @@ div.horizontal-tabs { } /** - * tab content + * tab content */ -div.field-group-htabs-wrapper .field-group-format-wrapper { +div.field-group-htabs-wrapper .field-group-format-wrapper { clear: both; padding: 0 0 0.6em; } @@ -95,4 +97,5 @@ div.field-group-htabs-wrapper .field-group-format-wrapper { display: block; position: absolute; top: -100000px; -} \ No newline at end of file + width: 100%; +} diff --git a/sites/all/modules/contrib/fields/field_group/horizontal-tabs/horizontal-tabs.js b/sites/all/modules/contrib/fields/field_group/horizontal-tabs/horizontal-tabs.js old mode 100755 new mode 100644 index c2ab3ee..6429f90 --- a/sites/all/modules/contrib/fields/field_group/horizontal-tabs/horizontal-tabs.js +++ b/sites/all/modules/contrib/fields/field_group/horizontal-tabs/horizontal-tabs.js @@ -1,205 +1,206 @@ -(function ($) { - -/** - * This script transforms a set of fieldsets into a stack of horizontal - * tabs. Another tab pane can be selected by clicking on the respective - * tab. - * - * Each tab may have a summary which can be updated by another - * script. For that to work, each fieldset has an associated - * 'horizontalTabCallback' (with jQuery.data() attached to the fieldset), - * which is called every time the user performs an update to a form - * element inside the tab pane. - */ -Drupal.behaviors.horizontalTabs = { - attach: function (context) { - $('.horizontal-tabs-panes', context).once('horizontal-tabs', function () { - var focusID = $(':hidden.horizontal-tabs-active-tab', this).val(); - var tab_focus; - - // Check if there are some fieldsets that can be converted to horizontal-tabs - var $fieldsets = $('> fieldset', this); - if ($fieldsets.length == 0) { - return; - } - - // Create the tab column. - var tab_list = $('
      '); - $(this).wrap('
      ').before(tab_list); - - // Transform each fieldset into a tab. - $fieldsets.each(function (i) { - var horizontal_tab = new Drupal.horizontalTab({ - title: $('> legend', this).text(), - fieldset: $(this) - }); - horizontal_tab.item.addClass('horizontal-tab-button-' + i); - tab_list.append(horizontal_tab.item); - $(this) - .removeClass('collapsible collapsed') - .addClass('horizontal-tabs-pane') - .data('horizontalTab', horizontal_tab); - if (this.id == focusID) { - tab_focus = $(this); - } - }); - - $('> li:first', tab_list).addClass('first'); - $('> li:last', tab_list).addClass('last'); - - if (!tab_focus) { - // If the current URL has a fragment and one of the tabs contains an - // element that matches the URL fragment, activate that tab. - if (window.location.hash && $(window.location.hash, this).length) { - tab_focus = $(window.location.hash, this).closest('.horizontal-tabs-pane'); - } - else { - tab_focus = $('> .horizontal-tabs-pane:first', this); - } - } - if (tab_focus.length) { - tab_focus.data('horizontalTab').focus(); - } - }); - } -}; - -/** - * The horizontal tab object represents a single tab within a tab group. - * - * @param settings - * An object with the following keys: - * - title: The name of the tab. - * - fieldset: The jQuery object of the fieldset that is the tab pane. - */ -Drupal.horizontalTab = function (settings) { - var self = this; - $.extend(this, settings, Drupal.theme('horizontalTab', settings)); - - this.link.click(function () { - self.focus(); - return false; - }); - - // Keyboard events added: - // Pressing the Enter key will open the tab pane. - this.link.keydown(function(event) { - if (event.keyCode == 13) { - self.focus(); - // Set focus on the first input field of the visible fieldset/tab pane. - $("fieldset.horizontal-tabs-pane :input:visible:enabled:first").focus(); - return false; - } - }); - - // Pressing the Enter key lets you leave the tab again. - this.fieldset.keydown(function(event) { - // Enter key should not trigger inside