├── LICENSE.txt ├── README.md ├── README.txt ├── css ├── index.php ├── nmt-plugin-manager.css └── nmt-site-info.css ├── includes ├── class-nmt-activator.php ├── class-nmt-deactivator.php ├── class-nmt-i18n.php ├── class-nmt-plugin-manager.php ├── class-nmt-site-info-db.php ├── class-nmt-site-info.php ├── class-nmt.php └── index.php ├── index.php ├── js ├── index.php ├── jquery.dataTables.min.js ├── nmt-plugins-screen.js ├── nmt-site-info.js └── nmt-site-plugins-tab.js ├── languages ├── index.php └── network-manager-tools.pot ├── network-manager-tools.php ├── partials ├── index.php └── nmt-site-info-table-display.php └── uninstall.php /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Network Manager Tools 2 | 3 | A multisite plugin that adds a number of helpful tools for network administrators. Many of these features are built primarily for open-style networks, but can be helpful for closed networks as well. 4 | 5 | ## Current Features 6 | 1. Site Information 7 | * Table of all sites, their ID, active theme, active plugins, created and last updated date 8 | * Sortable columns, pagination 9 | * Incremental search field searches entire table with auto-suggest for installed themes & plugins 10 | * Site (deactivate, delete, archive, spam, etc.) actions directly from this screen 11 | 1. Plugin Manager 12 | * New "network-enabled" status limits plugin availablity to single-site admins on the network in the same manner as themes 13 | * Limit plugin availability on a per-site basis through new Plugins tab on site-info.php screen 14 | * Activate/deactivate plugins on individual sites directly from aforementioned plugins screen on the network dashboard 15 | 16 | ## Planned Features 17 | 1. Admin Message 18 | * Post messages to the dashboard of individual sites on a network 19 | * Post to all sites, or subset of sites based on active theme, plugins, etc. 20 | * Add publish/expiration date for automatic posting/removal of messages 21 | 1. Support Admin 22 | * Some users (especially support staff) need access to all sites, but not network-level settings, this would allow you to set multiple levels of super admins. 23 | 1. Site Deprovisioner 24 | * Automatically deactivate and/or remove unused sites based on last updated date, amount of content, etc. -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | === Plugin Name === 2 | Contributors: earnjam 3 | Tags: multisite, network, manage 4 | Requires at least: 3.7 5 | Tested up to: 4.1 6 | Stable tag: trunk 7 | License: GPLv2 or later 8 | License URI: http://www.gnu.org/licenses/gpl-2.0.html 9 | 10 | A multisite plugin that adds a number of helpful tools for network administrators. 11 | 12 | == Description == 13 | 14 | # Current Features 15 | 1. ### Site Information 16 | * Table of all sites, their ID, active theme, active plugins, created and last updated date 17 | * Sortable columns, pagination 18 | * Incremental search field searches entire table with auto-suggest for installed themes & plugins 19 | * Site (deactivate, delete, archive, spam, etc.) actions directly from this screen 20 | 1. ### Plugin Manager 21 | * New "network-enabled" status limits plugin availablity to single-site admins on the network in the same manner as themes 22 | * Limit plugin availability on a per-site basis through new Plugins tab on site-info.php screen 23 | * Activate/deactivate plugins on individual sites directly from aforementioned plugins screen on the network dashboard 24 | 25 | == Installation == 26 | 27 | 1. Upload `network-manager-tools` to the `/wp-content/plugins/` directory 28 | 1. Network Activate the plugin through the 'Plugins' menu in the WordPress multisite network admin 29 | 30 | == Changelog == 31 | 32 | = 1.0 = 33 | * Initial release -------------------------------------------------------------------------------- /css/index.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | class NMT_Activator { 23 | 24 | /** 25 | * Short Description. (use period) 26 | * 27 | * Long Description. 28 | * 29 | * @since 1.0.0 30 | */ 31 | public static function activate() { 32 | 33 | // Create our table 34 | global $wpdb; 35 | 36 | $table_name = $wpdb->base_prefix . "nmt_site_info"; 37 | 38 | /* 39 | * We'll set the default character set and collation for this table. 40 | * If we don't do this, some characters could end up being converted 41 | * to just ?'s when saved in our table. 42 | */ 43 | $charset_collate = ''; 44 | 45 | if ( ! empty( $wpdb->charset ) ) { 46 | $charset_collate = "DEFAULT CHARACTER SET {$wpdb->charset}"; 47 | } 48 | 49 | if ( ! empty( $wpdb->collate ) ) { 50 | $charset_collate .= " COLLATE {$wpdb->collate}"; 51 | } 52 | 53 | $sql = "CREATE TABLE $table_name ( 54 | blog_id bigint(20) NOT NULL AUTO_INCREMENT, 55 | active_theme varchar(100) NOT NULL, 56 | active_plugins longtext NOT NULL, 57 | PRIMARY KEY (blog_id), 58 | KEY active_theme (active_theme) 59 | ) $charset_collate;"; 60 | 61 | 62 | require_once( ABSPATH . 'wp-admin/includes/upgrade.php' ); 63 | dbDelta( $sql ); 64 | 65 | 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /includes/class-nmt-deactivator.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | class NMT_Deactivator { 23 | 24 | /** 25 | * Deletes the tables installed by the plugin. 26 | * 27 | * Drops the active themes & plugins table 28 | * 29 | * @since 1.0.0 30 | */ 31 | public static function deactivate() { 32 | 33 | global $wpdb; 34 | 35 | $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->base_prefix}nmt_site_info;" ); 36 | 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /includes/class-nmt-i18n.php: -------------------------------------------------------------------------------- 1 | 25 | */ 26 | class NMT_i18n { 27 | 28 | /** 29 | * The domain specified for this plugin. 30 | * 31 | * @since 1.0.0 32 | * @access private 33 | * @var string $domain The domain identifier for this plugin. 34 | */ 35 | private $domain; 36 | 37 | /** 38 | * Load the plugin text domain for translation. 39 | * 40 | * @since 1.0.0 41 | */ 42 | public function load_plugin_textdomain() { 43 | 44 | load_plugin_textdomain( 45 | $this->domain, 46 | false, 47 | dirname( dirname( plugin_basename( __FILE__ ) ) ) . '/languages/' 48 | ); 49 | 50 | } 51 | 52 | /** 53 | * Set the domain equal to that of the specified domain. 54 | * 55 | * @since 1.0.0 56 | * @param string $domain The domain that represents the locale of this plugin. 57 | */ 58 | public function set_domain( $domain ) { 59 | $this->domain = $domain; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /includes/class-nmt-plugin-manager.php: -------------------------------------------------------------------------------- 1 | is_network_enabled( $plugin_file ) ) { 98 | $new_actions['enable'] = '' . __('Network Enable') . ''; 99 | } else { 100 | $new_actions['disable'] = '' . __('Network Disable') . ''; 101 | } 102 | } 103 | 104 | return array_merge( $new_actions, $actions ); 105 | 106 | } 107 | 108 | /** 109 | * @param $install_actions 110 | * @param $api 111 | * @param $plugin_file 112 | * 113 | * @return array 114 | * @author Ben Meredith 115 | */ 116 | 117 | public function plugin_installed_options_link( $install_actions, $api, $plugin_file ) { 118 | 119 | $new_link = '' . __('Network Enable') . ''; 120 | array_unshift( $install_actions, $new_link ); 121 | 122 | return $install_actions; 123 | 124 | } 125 | 126 | /** 127 | * @return array|mixed 128 | */ 129 | public function get_network_enabled_plugins() { 130 | 131 | if ( ! isset( $this->network_enabled_plugins ) ) { 132 | $this->network_enabled_plugins = ( get_site_option( 'nmt_allowedplugins' ) ) ? get_site_option( 'nmt_allowedplugins' ) : array(); 133 | } 134 | 135 | return $this->network_enabled_plugins; 136 | 137 | } 138 | 139 | /** 140 | * @param $plugin_file 141 | * 142 | * @return bool 143 | */ 144 | public function is_network_enabled( $plugin_file ) { 145 | 146 | $enabled = $this->get_network_enabled_plugins(); 147 | 148 | if ( isset ( $enabled[ $plugin_file ] ) ) { 149 | return true; 150 | } 151 | return false; 152 | 153 | } 154 | 155 | /** 156 | * @param array $enabled_plugins 157 | */ 158 | public function set_network_enabled_plugins( $enabled_plugins = array() ) { 159 | 160 | update_site_option( 'nmt_allowedplugins', $enabled_plugins ); 161 | $this->network_enabled_plugins = $enabled_plugins; 162 | 163 | } 164 | 165 | public function process_plugins( $plugins ) { 166 | 167 | global $action, $plugins, $plugin, $status, $wp_list_table; 168 | 169 | if ( isset( $_REQUEST['plugin_status'] ) && in_array( $_REQUEST['plugin_status'], array( 'active', 'inactive', 'enabled', 'disabled', 'recently_activated', 'upgrade', 'mustuse', 'dropins', 'search' ) ) ) 170 | $status = $_REQUEST['plugin_status']; 171 | 172 | switch( $action ) { 173 | case 'enable' : 174 | 175 | if ( ! is_multisite() || ! is_network_admin() || is_network_only_plugin( $plugin ) ) { 176 | wp_redirect( self_admin_url("plugins.php?plugin_status=$status") ); 177 | exit; 178 | } 179 | 180 | check_admin_referer('enable-plugin_' . $plugin); 181 | 182 | $enabled = $this->get_network_enabled_plugins(); 183 | if ( ! $this->is_network_enabled( $plugin ) ) { 184 | $enabled[ $plugin ] = time(); 185 | } 186 | ksort( $enabled ); 187 | $this->set_network_enabled_plugins( $enabled ); 188 | 189 | break; 190 | 191 | case 'disable' : 192 | 193 | if ( ! is_multisite() || ! is_network_admin() || is_network_only_plugin( $plugin ) ) { 194 | wp_redirect( self_admin_url("plugins.php?plugin_status=$status") ); 195 | exit; 196 | } 197 | 198 | check_admin_referer('disable-plugin_' . $plugin); 199 | 200 | $enabled = $this->get_network_enabled_plugins(); 201 | if ( $this->is_network_enabled( $plugin ) ) { 202 | unset ( $enabled[ $plugin ] ); 203 | } 204 | $this->set_network_enabled_plugins( $enabled ); 205 | 206 | break; 207 | 208 | case 'enable-selected' : 209 | 210 | check_admin_referer('bulk-plugins'); 211 | 212 | $enable_plugins = isset( $_POST['checked'] ) ? (array) $_POST['checked'] : array(); 213 | 214 | $enabled = $this->get_network_enabled_plugins(); 215 | 216 | foreach ( $enable_plugins as $plugin_file ) { 217 | // Only enable if it is not network activated and not network enabled 218 | if ( ! is_plugin_active_for_network( $plugin_file ) && ! $this->is_network_enabled( $plugin_file ) ) { 219 | $enabled[ $plugin_file ] = time(); 220 | } 221 | } 222 | 223 | ksort($enabled); 224 | $this->set_network_enabled_plugins( $enabled ); 225 | 226 | break; 227 | 228 | case 'disable-selected' : 229 | 230 | check_admin_referer('bulk-plugins'); 231 | 232 | $disable_plugins = isset( $_POST['checked'] ) ? (array) $_POST['checked'] : array(); 233 | 234 | $enabled = $this->get_network_enabled_plugins(); 235 | 236 | foreach ( $disable_plugins as $plugin_file ) { 237 | // Only disable if it is is network enabled and not network activated 238 | if ( ! is_plugin_active_for_network( $plugin_file ) && $this->is_network_enabled( $plugin_file ) ) { 239 | unset( $enabled[ $plugin_file ] ); 240 | } 241 | } 242 | 243 | $this->set_network_enabled_plugins( $enabled ); 244 | 245 | break; 246 | 247 | } 248 | 249 | $plugins['enabled'] = array(); 250 | $plugins['disabled'] = array(); 251 | 252 | foreach ( (array) $plugins['all'] as $plugin_file => $plugin_data ) { 253 | 254 | if ( $this->is_network_enabled( $plugin_file ) && ! is_plugin_active_for_network( $plugin_file ) ) { 255 | $plugins['enabled'][ $plugin_file ] = $plugin_data; 256 | } elseif ( ! is_plugin_active_for_network( $plugin_file ) ) { 257 | $plugins['disabled'][ $plugin_file ] = $plugin_data; 258 | } 259 | 260 | } 261 | 262 | // Only show the correct plugins if the filter view is set to enabled or disabled 263 | if ( 'enabled' == $status ) { 264 | $wp_list_table->items = $plugins['enabled']; 265 | } elseif ( 'disabled' == $status ) { 266 | $wp_list_table->items = $plugins['disabled']; 267 | } 268 | 269 | } 270 | 271 | 272 | /** 273 | * Extends the network plugins screen's views to add enabled and disabled 274 | * 275 | * @since 1.0.0 276 | * @param $views 277 | * 278 | * @return mixed 279 | */ 280 | public function get_views ( $views ) { 281 | 282 | global $totals, $plugins, $status; 283 | 284 | $totals['enabled'] = count( $plugins['enabled'] ); 285 | $totals['disabled'] = count( $plugins['disabled'] ); 286 | 287 | // We want to insert the new links into the 3rd and 4th position, but maintain the array keys 288 | // so the proper class gets applied to the
  • 289 | foreach ( $views as $key => $link ) { 290 | if ( 'upgrade' == $key ) { 291 | $current = ( 'enabled' == $status ) ? ' class=current' : ''; 292 | $new_views['enabled'] = 'Enabled (' . $totals['enabled'] . ')'; 293 | $current = ( 'disabled' == $status ) ? ' class=current' : ''; 294 | $new_views['disabled'] = 'Disabled (' . $totals['disabled'] . ')'; 295 | } 296 | $new_views[ $key ] = $link; 297 | } 298 | 299 | return $new_views; 300 | 301 | } 302 | 303 | /** 304 | * 305 | */ 306 | public function site_plugins_tab () { 307 | global $pagenow; 308 | 309 | $id = isset( $_REQUEST['id'] ) ? intval( $_REQUEST['id'] ) : 0; 310 | 311 | if ( ! $id ) 312 | wp_die( __('Invalid site ID.') ); 313 | 314 | $details = get_blog_details( $id ); 315 | if ( ! can_edit_network( $details->site_id ) ) 316 | wp_die( __( 'You do not have permission to access this page.' ) ); 317 | 318 | switch_to_blog($id); 319 | 320 | $message = false; 321 | $plugins = get_plugins(); 322 | $active_plugins = get_option( 'active_plugins' ); 323 | $site_allowed_plugins = ( get_option( 'nmt_allowedplugins' ) ) ? get_option ( 'nmt_allowedplugins' ) : array(); 324 | $filter = ( isset ( $_GET['plugin_status'] ) && in_array( $_GET['plugin_status'], array( 'all', 'active', 'enabled' ) ) ) ? $_GET['plugin_status'] : 'all'; 325 | 326 | if ( isset( $_GET['action'] ) ) { 327 | $action_plugin = $_GET['plugin_name']; 328 | if ( array_key_exists( $action_plugin, $plugins ) ) { 329 | switch ( $_GET['action'] ) { 330 | case 'activate_plugin': 331 | activate_plugin( $action_plugin ); 332 | $message = 'Plugin activated'; 333 | break; 334 | case 'deactivate_plugin': 335 | deactivate_plugins( $action_plugin, false, false ); 336 | $message = 'Plugin deactivated.'; 337 | break; 338 | case 'enable_plugin': 339 | $site_allowed_plugins[$action_plugin] = time(); 340 | ksort( $site_allowed_plugins ); 341 | update_option( 'nmt_allowedplugins', $site_allowed_plugins ); 342 | $message = 'Plugin enabled.'; 343 | break; 344 | case 'disable_plugin': 345 | unset( $site_allowed_plugins[$action_plugin] ); 346 | update_option( 'nmt_allowedplugins', $site_allowed_plugins ); 347 | $message = 'Plugin disabled.'; 348 | break; 349 | default: 350 | $message = 'Error: Invalid action.'; 351 | break; 352 | } 353 | } else { 354 | $message = 'Error: Invalid plugin name.'; 355 | } 356 | } 357 | 358 | $enabled_plugins = $this->get_network_enabled_plugins() + $site_allowed_plugins; 359 | 360 | $site_url_no_http = preg_replace( '#^http(s)?://#', '', get_blogaddress_by_id( $id ) ); 361 | $title_site_url_linked = sprintf( __('Edit Site: %2$s'), get_blogaddress_by_id( $id ), $site_url_no_http ); 362 | 363 | ?> 364 |
    365 |

    366 |

    '.$message.'

    '; 368 | } ?> 369 | 385 | 390 | 391 | 392 | 393 | 394 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 408 | 409 | 410 | 411 | 412 | 413 | $plugin_data ) { 414 | 415 | $enabled = false; 416 | $active = false; 417 | $row_class = 'inactive'; 418 | 419 | if ( isset( $enabled_plugins[ $plugin ] ) ) { 420 | $enabled = true; 421 | $row_class = 'enabled'; 422 | } 423 | if ( is_plugin_active( $plugin ) ) { 424 | $active = true; 425 | $row_class = 'active'; 426 | } 427 | 428 | if ( $filter == 'enabled' && ! $enabled ) { 429 | continue; 430 | } 431 | 432 | if ( $filter == 'active' && ! $active ) { 433 | continue; 434 | } 435 | 436 | $network = is_plugin_active_for_network( $plugin ); 437 | 438 | $checkbox_id = "checkbox_" . md5( $plugin_data['Name']); 439 | $checkbox = "" 440 | . ""; 441 | 442 | 443 | ?> 444 | 445 | 446 | 471 | 489 | 490 | 491 |
    395 | 396 | 397 | PluginDescription
    405 | 406 | 407 | PluginDescription
    447 | 448 |
    449 | 'deactivate_plugin', 'plugin_name' => urlencode( $plugin ) ); 454 | echo 'Deactivate'; 455 | } else { 456 | $actions = array( 'action' => 'activate_plugin', 'plugin_name' => urlencode( $plugin ) ); 457 | echo 'Activate'; 458 | if ( $this->is_network_enabled( $plugin ) ) { 459 | echo ' | Network Enabled'; 460 | } elseif ( isset( $enabled_plugins[$plugin] ) ) { 461 | $actions = array( 'action' => 'disable_plugin', 'plugin_name' => urlencode( $plugin ) ); 462 | echo ' | Disable'; 463 | } else { 464 | $actions = array( 'action' => 'enable_plugin', 'plugin_name' => urlencode( $plugin ) ); 465 | echo ' | Enable'; 466 | } 467 | 468 | } ?> 469 |
    470 |
    472 |

    473 |
    474 | ' . $plugin_data['Author'] . ''; 481 | $plugin_meta[] = sprintf( __( 'By %s' ), $author ); 482 | } 483 | if ( ! empty( $plugin_data['PluginURI'] ) ) 484 | $plugin_meta[] = '' . __( 'Visit plugin site' ) . ''; 485 | echo implode( ' | ', $plugin_meta ); 486 | ?> 487 |
    488 |
    492 | 493 | 494 | 495 | $plugin_data) { 519 | 520 | if ( isset ( $network_allowed[ $plugin ] ) || isset ( $site_allowed[ $plugin ] ) ) { 521 | // Do nothing because the plugin has been enabled on a site or network level 522 | } else { 523 | // The plugin is disabled, so unset it from the list of all plugins 524 | unset( $all_plugins[ $plugin ] ); 525 | } 526 | 527 | } 528 | 529 | return $all_plugins; 530 | } 531 | 532 | } 533 | 534 | $nmt_plugin_manager = new NMT_Plugin_Manager(); -------------------------------------------------------------------------------- /includes/class-nmt-site-info-db.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class NMT_DB { 14 | 15 | private $db; 16 | 17 | private $info_table; 18 | 19 | private $prefix; 20 | 21 | private $installed_themes; 22 | 23 | /** 24 | * 25 | */ 26 | public function __construct() { 27 | 28 | // Map $wpdb object 29 | global $wpdb; 30 | $this->db = $wpdb; 31 | $this->prefix = $wpdb->base_prefix; 32 | 33 | // Store the name of our Site Information table for later use 34 | $this->info_table = $this->prefix . 'nmt_site_info'; 35 | 36 | } 37 | 38 | /** 39 | * Adds data for an individual site to the Site Information table 40 | * 41 | * Long Description. 42 | * 43 | * @since 1.0.0 44 | */ 45 | public function add_info( $site_id, $theme_name, $active_plugins ) { 46 | 47 | $data = array( 48 | 'blog_id' => $site_id, 49 | 'active_theme' => $theme_name, 50 | 'active_plugins' => $active_plugins 51 | ); 52 | 53 | $this->db->insert( $this->info_table, $data ); 54 | 55 | } 56 | 57 | /** 58 | * Updates the active theme value for a specific site. 59 | * 60 | * Long Description. 61 | * 62 | * @since 1.0.0 63 | * 64 | * @param $theme_name 65 | */ 66 | public function update_theme( $theme_name, $site_id = 0 ) { 67 | 68 | if ( 0 == $site_id ) { 69 | $site_id = $this->db->blogid; 70 | } 71 | 72 | $data = array( 73 | 'active_theme' => $theme_name 74 | ); 75 | 76 | $this->update_data( $site_id, $data ); 77 | 78 | } 79 | 80 | /** 81 | * Updates the active plugins list for a specific site. 82 | * 83 | * Long Description. 84 | * 85 | * @since 1.0.0 86 | */ 87 | public function update_plugins( $active_plugins, $site_id = 0 ) { 88 | 89 | if ( 0 == $site_id ) { 90 | $site_id = $this->db->blogid; 91 | } 92 | 93 | $data = array( 94 | 'active_plugins' => maybe_serialize($active_plugins) 95 | ); 96 | 97 | $this->update_data( $site_id, $data ); 98 | 99 | } 100 | 101 | /** 102 | * Generic function for updating data in the site information table 103 | * 104 | * @since 1.0.0 105 | */ 106 | public function update_data( $site_id, $data ) { 107 | 108 | $where = array( 109 | 'blog_id' => $site_id 110 | ); 111 | 112 | $result = $this->db->update( $this->info_table, $data, $where ); 113 | 114 | } 115 | 116 | /** 117 | * Function that runs on deactivated_plugin hook 118 | * 119 | * @since 1.0.0 120 | * 121 | * @param $plugin string Dir/filename of the plugin that was deactivated 122 | */ 123 | public function plugin_deactivated( $plugin ) { 124 | 125 | $current = get_option( 'active_plugins', $this->db->blogid ); 126 | $key = array_search( $plugin, $current ); 127 | if ( false !== $key ) { 128 | unset( $current[ $key ] ); 129 | } 130 | 131 | $this->update_plugins( $current, $this->db->blogid ); 132 | 133 | } 134 | 135 | /** 136 | * Function that runs on activated_plugin hook 137 | * 138 | * @since 1.0.0 139 | * 140 | * @param $plugin string Dir/filename of the plugin that was deactivated 141 | */ 142 | public function plugin_activated( $plugin ) { 143 | 144 | $current = get_option( 'active_plugins', $this->db->blogid ); 145 | 146 | $this->update_plugins( $current, $this->db->blogid ); 147 | 148 | } 149 | 150 | /** 151 | * Checks to see if the site information table has had data populated 152 | * 153 | * @since 1.0.0 154 | * 155 | * @return bool 156 | */ 157 | public function has_site_data() { 158 | 159 | $data = $this->db->get_results( "SELECT * FROM $this->info_table WHERE blog_id = 1" ); 160 | 161 | if ( $data ) { 162 | return true; 163 | } 164 | 165 | return false; 166 | 167 | } 168 | 169 | /** 170 | * Builds out the data for the site information table 171 | * 172 | * @since 1.0.0 173 | */ 174 | public function build_site_info() { 175 | 176 | $n = ( isset( $_POST['n'] ) ) ? intval( $_POST['n'] ) : 0; 177 | $total = ( isset( $_POST['total'] ) ) ? intval( $_POST['total'] ) : $this->get_last_blog_id(); 178 | 179 | $blogs_table = $this->db->blogs; 180 | 181 | $sql = $this->db->prepare( "SELECT * FROM $blogs_table WHERE site_id = %d ORDER BY registered ASC LIMIT %d, 100", $this->db->siteid, $n ); 182 | 183 | $blogs = $this->db->get_results( $sql , ARRAY_A ); 184 | 185 | if ( ! empty( $blogs ) ) { 186 | foreach ( $blogs as $blog ) { 187 | $options = $this->get_options_data( $blog['blog_id'] ); 188 | $this->add_info( $blog['blog_id'], $options[1]['option_value'], $options[0]['option_value'] ); 189 | $n++; 190 | } 191 | } else { 192 | $n = $total; 193 | } 194 | 195 | $response = array( 196 | 'n' => $n, 197 | 'total' => $total 198 | ); 199 | 200 | wp_send_json( $response ); 201 | 202 | } 203 | 204 | /** 205 | * Builds the information index 206 | * 207 | * @since 1.0.0 208 | */ 209 | public function build_index() { 210 | 211 | // Get a list of all installed themes 212 | $this->installed_themes = wp_get_themes(); 213 | 214 | // Get a list of all sites. 215 | $sites = wp_get_sites(); 216 | 217 | foreach ( $sites as $site ) { 218 | 219 | $options = $this->get_options_data( $site['blog_id'] ); 220 | 221 | // build our data array to pass to add_info 222 | foreach ( $options as $option ) { 223 | 224 | // Change the array key to active_theme and make it the theme name instead of stylesheet directory 225 | if ( 'stylesheet' == $option->option_name ) { 226 | $option->option_name = 'active_theme'; 227 | $theme = wp_get_theme( $option->option_value ); 228 | $option->option_value = $theme->Name; 229 | } 230 | 231 | $data[ $option->option_name ] = $option->option_value; 232 | 233 | } 234 | 235 | $this->add_info( $site['blog_id'], $data['active_theme'], $data['active_plugins'] ); 236 | 237 | } 238 | 239 | } 240 | 241 | /** 242 | * Get the active theme and plugins for an individual site directly from the source 243 | * 244 | * @since 1.0.0 245 | * 246 | * @param $blog_id 247 | * 248 | * @return mixed 249 | */ 250 | public function get_options_data( $blog_id ) { 251 | 252 | $table = $this->_get_options_table_name( $blog_id ); 253 | 254 | $sql = "SELECT option_name, option_value FROM $table WHERE option_name IN ('stylesheet','active_plugins')"; 255 | 256 | $result = $this->db->get_results( $sql, ARRAY_A ); 257 | 258 | return $result; 259 | 260 | 261 | } 262 | 263 | /** 264 | * Get the options table name for an individual site. 265 | * 266 | * @since 1.0.0 267 | * 268 | * @param $blog_id 269 | * 270 | * @return string 271 | */ 272 | private function _get_options_table_name( $blog_id ) { 273 | 274 | $table_name = $this->prefix . $blog_id . '_options'; 275 | 276 | // Before returning, if this is the first site, we need to check to see if this 277 | // was an old WPMU install and uses the $prefix_1_options table format instead of $prefix_options 278 | if ( 1 == $blog_id ) { 279 | 280 | $result = $this->db->get_results( "SELECT option_value FROM $table_name WHERE option_name='siteurl'" ); 281 | 282 | if ( ! $result ) { 283 | $table_name = $this->prefix . 'options'; 284 | } 285 | 286 | } 287 | 288 | return $table_name; 289 | 290 | } 291 | 292 | /** 293 | * Returns all the data from the site information table 294 | * 295 | * @since 1.0.0 296 | * 297 | * @return mixed 298 | */ 299 | public function get_site_data() { 300 | 301 | $data = $this->db->get_results( "SELECT * FROM " . $this->info_table . " INNER JOIN " . $this->db->blogs ." USING (blog_id)" ); 302 | 303 | return $data; 304 | 305 | } 306 | 307 | /** 308 | * Gets the blog ID for the last site created on the network 309 | * 310 | * @since 1.0.0 311 | * 312 | * @return integer 313 | */ 314 | public function get_last_blog_id() { 315 | 316 | $table = $this->db->blogs; 317 | 318 | $id = $this->db->get_results( "SELECT blog_id FROM $table ORDER BY blog_id DESC LIMIT 1" ); 319 | 320 | return $id[0]->blog_id; 321 | 322 | } 323 | 324 | } 325 | -------------------------------------------------------------------------------- /includes/class-nmt-site-info.php: -------------------------------------------------------------------------------- 1 | load_dependencies(); 46 | $this->register_hooks(); 47 | 48 | } 49 | 50 | /** 51 | * Pull in any required classes to handle the functionality 52 | * 53 | * @since 1.0.0 54 | * @todo require the NMT_DB class 55 | */ 56 | private function load_dependencies() { 57 | 58 | /** 59 | * The class responsible for orchestrating interactions with the site info table 60 | */ 61 | require_once plugin_dir_path( dirname( __FILE__ ) ) . 'includes/class-nmt-site-info-db.php'; 62 | 63 | $this->db = new NMT_DB(); 64 | 65 | } 66 | 67 | /** 68 | * Registers all the action hooks for the Site Info Table 69 | * 70 | * @since 1.0.0 71 | * @todo register new site hook 72 | * @todo register plugin status change hook 73 | */ 74 | private function register_hooks() { 75 | 76 | // Register our admin pages 77 | add_action( 'network_admin_menu', array( $this, 'add_pages' ) ); 78 | 79 | // Add our Styles & Scripts 80 | add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_styles' ) ); 81 | add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); 82 | 83 | // Register the AJAX callback for building the site info table 84 | add_action( 'wp_ajax_build_site_info', array( $this->db, 'build_site_info' ) ); 85 | 86 | // Register our 3 types of update hooks: New site, theme switch, plugin activation/deactivation 87 | add_action( 'switch_theme', array( $this->db, 'update_theme' ) ); 88 | add_action( 'activated_plugin', array( $this->db, 'plugin_activated' ) ); 89 | add_action( 'deactivated_plugin', array( $this->db, 'plugin_deactivated' ) ); 90 | add_action( 'wpmu_new_blog', array( $this->db, 'add_info')); 91 | 92 | } 93 | 94 | /** 95 | * Register all the styles for the Site Info Table functionality 96 | * 97 | * @since 1.0.0 98 | */ 99 | public function enqueue_styles( $hook ) { 100 | 101 | if ($hook == 'sites_page_nmt_site_info') { 102 | wp_enqueue_style( 'nmt-site-info', dirname( plugin_dir_url( __FILE__ ) ) . '/css/nmt-site-info.css' ); 103 | } 104 | 105 | } 106 | 107 | /** 108 | * Register all the scripts for the Site Info Table functionality 109 | * 110 | * @since 1.0.0 111 | */ 112 | public function enqueue_scripts( $hook ) { 113 | 114 | if ($hook == 'sites_page_nmt_site_info') { 115 | wp_enqueue_script( 'jquery-ui-progressbar' ); 116 | wp_enqueue_script( 'jquery-ui-autocomplete' ); 117 | wp_enqueue_script( 'datatables', dirname( plugin_dir_url( __FILE__ ) ) . '/js/jquery.dataTables.min.js', array( 'jquery' ) ); 118 | wp_enqueue_script( 'nmt-site-info', dirname( plugin_dir_url( __FILE__ ) ) . '/js/nmt-site-info.js', array( 'jquery' ) ); 119 | wp_localize_script( 'nmt-site-info', 'wp_addons', $this->get_addons() ); 120 | } 121 | 122 | } 123 | 124 | 125 | /** 126 | * Returns alphabetically sorted array of installed themes and plugins 127 | * 128 | * @since 1.0.0 129 | * 130 | * @return array 131 | */ 132 | public function get_addons() { 133 | 134 | $plugins = get_plugins(); 135 | $themes = wp_get_themes(); 136 | 137 | $terms = array_merge( $plugins, $themes ); 138 | 139 | foreach ( $terms as $term ) { 140 | if ( $term['Name'] != '' ) { 141 | $addons[] = $term['Name']; 142 | } 143 | } 144 | 145 | sort( $addons ); 146 | 147 | return $addons; 148 | } 149 | 150 | /** 151 | * Register the Network Admin Pages 152 | * 153 | * @since 1.0.0 154 | * @todo move Admin Message to the admin message class 155 | */ 156 | public function add_pages() { 157 | 158 | $this->admin_pages['site_info'] = add_submenu_page( 'sites.php', 'Site Information', 'Site Information', 'manage_network', 'nmt_site_info', array( $this, 'site_information' ) ); 159 | // Future Admin Message functionality 160 | // $this->admin_pages['admin_message'] = add_menu_page( 'Admin Messages', 'Admin Messages', 'manage_network', 'admin_message', array( $this, 'admin_message' ), 'dashicons-megaphone', 3 ); 161 | 162 | // Add action hook to generate the help tab for each admin page 163 | foreach ( $this->admin_pages as $page ) { 164 | add_action( 'load-'.$page, array( $this, 'help_tabs' ) ); 165 | } 166 | 167 | } 168 | 169 | /** 170 | * Setup the output for the Site Information Page 171 | * 172 | * @since 1.0.0 173 | */ 174 | public function site_information() { 175 | 176 | // Output the Site Info Page 177 | echo '

    Network Site Information

    '; 178 | 179 | if ( ! $this->db->has_site_data() ) { 180 | 181 | echo '

    Thanks for installing Network Manager Tools! This screen will help you easily see which 182 | sites on your network are using which theme and plugin.

    '; 183 | echo '

    In order to make displaying the individual site data as efficient as possible, 184 | we need to do an initial scan of the database to build the index of active themes and plugins. 185 | This may take some time depending on the size of your network.

    '; 186 | 187 | echo '
    '; 188 | echo ''; 189 | echo '
    '; 190 | echo ''; 191 | 192 | } else { 193 | $data = $this->db->get_site_data(); 194 | $installed_plugins = get_plugins(); 195 | $installed_themes = wp_get_themes(); 196 | require_once dirname( plugin_dir_path( __FILE__ ) ) . '/partials/nmt-site-info-table-display.php'; 197 | } 198 | 199 | echo '
    '; 200 | 201 | } 202 | 203 | 204 | /** 205 | * Setup the output for the help tabs for each admin page in this module 206 | * 207 | * @since 1.0.0 208 | */ 209 | public function help_tabs() { 210 | 211 | $screen = get_current_screen(); 212 | 213 | switch ( $screen->base ) { 214 | 215 | case 'sites_page_nmt_site_info-network': 216 | 217 | $screen->add_help_tab( array( 218 | 'id' => 'overview', 219 | 'title' => __( 'Overview' ), 220 | 'content' => 221 | '

    The Site Information Screen is a better way of managing the sites in your Multisite Network.

    ' . 222 | '

    From here you can: ' . 223 | '

      ' . 224 | '
    • Search for sites in your network
    • ' . 225 | '
    • Modify or remove sites from your network
    • ' . 226 | '
    • Search for a specific theme or plugin to see which sites are using it
    • ' . 227 | '
    ' 228 | ) ); 229 | $screen->add_help_tab( array( 230 | 'id' => 'site-actions', 231 | 'title' => __( 'Site Actions' ), 232 | 'content' => 233 | '

    Hovering over each site reveals seven actions you can perform on that site (three for the primary site):

    ' . 234 | '
      ' . 235 | '
    • Clicking Dashboard takes you to the Dashboard screen for that site
    • ' . 236 | '
    • Clicking Deactivate, Archive, or Spam performs the specified action with a confirmation screen
    • ' . 237 | '
    • Clicking Delete permanently deletes a site from your network, following a confirmation screen
    • ' . 238 | '
    • Clicking Visit takes you to the front-end view of the site
    • ' . 239 | '
    ' 240 | ) ); 241 | $screen->add_help_tab( array( 242 | 'id' => 'searching', 243 | 'title' => __( 'Searching' ), 244 | 'content' => 245 | '

    By default the table displays 10 sites, but the search box will perform an instant search over the entire network.

    ' . 246 | '

    The search features an autocomplete list pre-filled with all of the installed themes and plugins.

    ' . 247 | '

    The table will be filtered with every letter typed. It finds exact matches based on the entire search field. For instance:

    ' . 248 | '
    A search for "Network Manager" would match "Network Manager Tools", however a search for "Network Tools" would not find this plugin.
    ' . 249 | '

    This is so that you can find specific themes, plugins, sites and administrators when you may have a number of similar names across the network.

    ' 250 | 251 | ) ); 252 | 253 | $screen->set_help_sidebar( 254 | '

    ' . __( 'For more information:' ) . '

    ' . 255 | '

    ' . __( 'Documentation on Site Management' ) . '

    ' . 256 | '

    ' . __( 'Support Forums' ) . '

    ' 257 | ); 258 | 259 | break; 260 | 261 | } 262 | 263 | } 264 | 265 | /** 266 | * Gets array of admin page slugs that are registered 267 | * 268 | * @return array 269 | * 270 | * @since 1.0.0 271 | */ 272 | public function get_admin_pages() { 273 | return $this->admin_pages; 274 | } 275 | 276 | /** 277 | * Gets the NMT_DB object to be used in other locations. 278 | * 279 | * @return object 280 | */ 281 | public function get_db() { 282 | return $this->db; 283 | } 284 | 285 | } 286 | 287 | $nmt_site_info = new NMT_Site_Info(); -------------------------------------------------------------------------------- /includes/class-nmt.php: -------------------------------------------------------------------------------- 1 | 26 | */ 27 | class Network_Manager_Tools { 28 | 29 | /** 30 | * The unique identifier of this plugin. 31 | * 32 | * @since 1.0.0 33 | * @access protected 34 | * @var string $plugin_name The string used to uniquely identify this plugin. 35 | */ 36 | protected $plugin_name; 37 | 38 | /** 39 | * The current version of the plugin. 40 | * 41 | * @since 1.0.0 42 | * @access protected 43 | * @var string $version The current version of the plugin. 44 | */ 45 | protected $version; 46 | 47 | 48 | /** 49 | * Site info database object for orchestrating interactions with the site info table 50 | * 51 | * @since 1.0.0 52 | * @access protected 53 | * @var object $nmt_db Orchestrates interactions with site info table 54 | */ 55 | protected $nmt_db; 56 | 57 | /** 58 | * Private boolean to be sure our plugin is only instantiated once 59 | * 60 | * @since 1.0.0 61 | * @access private 62 | * @var bool $instance Plugin instantiation status 63 | */ 64 | private static $instance = false; 65 | 66 | 67 | /** 68 | * Define the core functionality of the plugin. 69 | * 70 | * Set the plugin name and the plugin version that can be used throughout the plugin. 71 | * Load the dependencies, Load the modules and define the locale 72 | * 73 | * @since 1.0.0 74 | */ 75 | public function __construct() { 76 | 77 | $this->plugin_name = 'network-manager-tools'; 78 | $this->version = '1.0.0'; 79 | 80 | $this->load_dependencies(); 81 | $this->load_modules(); 82 | $this->set_locale(); 83 | 84 | } 85 | 86 | 87 | /** 88 | * Instantiates our plugin only if one does not exist 89 | * 90 | * @return bool | Network_Manager_Tools 91 | */ 92 | public static function get_instance() { 93 | if ( ! self::$instance ) { 94 | self::$instance = new self(); 95 | } 96 | return self::$instance; 97 | } 98 | 99 | /** 100 | * Load the required dependencies for this plugin. 101 | * 102 | * Include the following files that make up the plugin: 103 | * 104 | * - NMT_i18n. Defines internationalization functionality. 105 | * 106 | * @since 1.0.0 107 | * @access private 108 | */ 109 | private function load_dependencies() { 110 | 111 | /** 112 | * The class responsible for defining internationalization functionality 113 | * of the plugin. 114 | */ 115 | require_once plugin_dir_path( dirname( __FILE__ ) ) . 'includes/class-nmt-i18n.php'; 116 | 117 | } 118 | 119 | /** 120 | * Loads the individual NMT Modules 121 | * 122 | * @since 1.0.0 123 | * @access private 124 | * @todo Include the various existing modules 125 | */ 126 | private function load_modules() { 127 | 128 | /** 129 | * The class responsible for the site info table 130 | */ 131 | require_once plugin_dir_path( dirname( __FILE__ ) ) . 'includes/class-nmt-site-info.php'; 132 | 133 | require_once plugin_dir_path( dirname( __FILE__ ) ) . 'includes/class-nmt-plugin-manager.php'; 134 | 135 | } 136 | 137 | /** 138 | * Define the locale for this plugin for internationalization. 139 | * 140 | * Uses the Plugin_Name_i18n class in order to set the domain and to register the hook 141 | * with WordPress. 142 | * 143 | * @since 1.0.0 144 | * @access private 145 | */ 146 | private function set_locale() { 147 | 148 | $plugin_i18n = new NMT_i18n(); 149 | $plugin_i18n->set_domain( $this->get_plugin_name() ); 150 | 151 | add_action( 'plugins_loaded', array( $plugin_i18n, 'load_plugin_textdomain' ) ); 152 | 153 | } 154 | 155 | 156 | /** 157 | * The name of the plugin used to uniquely identify it within the context of 158 | * WordPress and to define internationalization functionality. 159 | * 160 | * @since 1.0.0 161 | * @return string The name of the plugin. 162 | */ 163 | public function get_plugin_name() { 164 | return $this->plugin_name; 165 | } 166 | 167 | /** 168 | * Retrieve the version number of the plugin. 169 | * 170 | * @since 1.0.0 171 | * @return string The version number of the plugin. 172 | */ 173 | public function get_version() { 174 | return $this->version; 175 | } 176 | 177 | /** 178 | * The reference to the class that orchestrates interaction with the site info table. 179 | * 180 | * @since 1.0.0 181 | * @return NMT_Loader Orchestrates interaction with the site info table. 182 | */ 183 | public function get_nmt_db() { 184 | return $this->$nmt_db; 185 | } 186 | 187 | } 188 | -------------------------------------------------------------------------------- /includes/index.php: -------------------------------------------------------------------------------- 1 | ").css({position:"absolute",top:0,left:0,height:1,width:1,overflow:"hidden"}).append(g("
    ").css({position:"absolute",top:1,left:1,width:100, 7 | overflow:"scroll"}).append(g('
    ').css({width:"100%",height:10}))).appendTo("body"),c=b.find(".test");a.bScrollOversize=100===c[0].offsetWidth;a.bScrollbarLeft=1!==c.offset().left;b.remove()}function gb(a,b,c,e,d,f){var h,i=!1;c!==l&&(h=c,i=!0);for(;e!==d;)a.hasOwnProperty(e)&&(h=i?b(h,a[e],e,a):a[e],i=!0,e+=f);return h}function Ea(a,b){var c=p.defaults.column,e=a.aoColumns.length,c=g.extend({},p.models.oColumn,c,{nTh:b?b:P.createElement("th"),sTitle:c.sTitle?c.sTitle:b?b.innerHTML: 8 | "",aDataSort:c.aDataSort?c.aDataSort:[e],mData:c.mData?c.mData:e,idx:e});a.aoColumns.push(c);c=a.aoPreSearchCols;c[e]=g.extend({},p.models.oSearch,c[e]);ja(a,e,null)}function ja(a,b,c){var b=a.aoColumns[b],e=a.oClasses,d=g(b.nTh);if(!b.sWidthOrig){b.sWidthOrig=d.attr("width")||null;var f=(d.attr("style")||"").match(/width:\s*(\d+[pxem%]+)/);f&&(b.sWidthOrig=f[1])}c!==l&&null!==c&&(eb(c),G(p.defaults.column,c),c.mDataProp!==l&&!c.mData&&(c.mData=c.mDataProp),c.sType&&(b._sManualType=c.sType),c.className&& 9 | !c.sClass&&(c.sClass=c.className),g.extend(b,c),D(b,c,"sWidth","sWidthOrig"),"number"===typeof c.iDataSort&&(b.aDataSort=[c.iDataSort]),D(b,c,"aDataSort"));var h=b.mData,i=W(h),j=b.mRender?W(b.mRender):null,c=function(a){return"string"===typeof a&&-1!==a.indexOf("@")};b._bAttrSrc=g.isPlainObject(h)&&(c(h.sort)||c(h.type)||c(h.filter));b.fnGetData=function(a,b,c){var e=i(a,b,l,c);return j&&b?j(e,b,a,c):e};b.fnSetData=function(a,b,c){return Q(h)(a,b,c)};"number"!==typeof h&&(a._rowReadObject=!0);a.oFeatures.bSort|| 10 | (b.bSortable=!1,d.addClass(e.sSortableNone));a=-1!==g.inArray("asc",b.asSorting);c=-1!==g.inArray("desc",b.asSorting);!b.bSortable||!a&&!c?(b.sSortingClass=e.sSortableNone,b.sSortingClassJUI=""):a&&!c?(b.sSortingClass=e.sSortableAsc,b.sSortingClassJUI=e.sSortJUIAscAllowed):!a&&c?(b.sSortingClass=e.sSortableDesc,b.sSortingClassJUI=e.sSortJUIDescAllowed):(b.sSortingClass=e.sSortable,b.sSortingClassJUI=e.sSortJUI)}function X(a){if(!1!==a.oFeatures.bAutoWidth){var b=a.aoColumns;Fa(a);for(var c=0,e=b.length;c< 11 | e;c++)b[c].nTh.style.width=b[c].sWidth}b=a.oScroll;(""!==b.sY||""!==b.sX)&&Y(a);u(a,null,"column-sizing",[a])}function ka(a,b){var c=Z(a,"bVisible");return"number"===typeof c[b]?c[b]:null}function $(a,b){var c=Z(a,"bVisible"),c=g.inArray(b,c);return-1!==c?c:null}function aa(a){return Z(a,"bVisible").length}function Z(a,b){var c=[];g.map(a.aoColumns,function(a,d){a[b]&&c.push(d)});return c}function Ga(a){var b=a.aoColumns,c=a.aoData,e=p.ext.type.detect,d,f,h,i,j,g,m,o,k;d=0;for(f=b.length;do[f])e(m.length+o[f],n);else if("string"===typeof o[f]){i=0;for(j=m.length;ib&&a[d]--; -1!=e&&c===l&& 19 | a.splice(e,1)}function ca(a,b,c,e){var d=a.aoData[b],f,h=function(c,f){for(;c.childNodes.length;)c.removeChild(c.firstChild);c.innerHTML=v(a,b,f,"display")};if("dom"===c||(!c||"auto"===c)&&"dom"===d.src)d._aData=ma(a,d,e,e===l?l:d._aData).data;else{var i=d.anCells;if(i)if(e!==l)h(i[e],e);else{c=0;for(f=i.length;c").appendTo(h));b=0;for(c= 23 | m.length;btr").attr("role","row");g(h).find(">tr>th, >tr>td").addClass(n.sHeaderTH);g(i).find(">tr>th, >tr>td").addClass(n.sFooterTH);if(null!==i){a=a.aoFooter[0];b=0;for(c=a.length;b=a.fnRecordsDisplay()?0:h,a.iInitDisplayStart=-1);var h=a._iDisplayStart,n=a.fnDisplayEnd();if(a.bDeferLoading)a.bDeferLoading= 26 | !1,a.iDraw++,B(a,!1);else if(i){if(!a.bDestroying&&!jb(a))return}else a.iDraw++;if(0!==j.length){f=i?a.aoData.length:n;for(i=i?0:h;i",{"class":d? 27 | e[0]:""}).append(g("",{valign:"top",colSpan:aa(a),"class":a.oClasses.sRowEmpty}).html(c))[0];u(a,"aoHeaderCallback","header",[g(a.nTHead).children("tr")[0],Ka(a),h,n,j]);u(a,"aoFooterCallback","footer",[g(a.nTFoot).children("tr")[0],Ka(a),h,n,j]);e=g(a.nTBody);e.children().detach();e.append(g(b));u(a,"aoDrawCallback","draw",[a]);a.bSorted=!1;a.bFiltered=!1;a.bDrawing=!1}}function M(a,b){var c=a.oFeatures,e=c.bFilter;c.bSort&&kb(a);e?fa(a,a.oPreviousSearch):a.aiDisplay=a.aiDisplayMaster.slice(); 28 | !0!==b&&(a._iDisplayStart=0);a._drawHold=b;L(a);a._drawHold=!1}function lb(a){var b=a.oClasses,c=g(a.nTable),c=g("
    ").insertBefore(c),e=a.oFeatures,d=g("
    ",{id:a.sTableId+"_wrapper","class":b.sWrapper+(a.nTFoot?"":" "+b.sNoFooter)});a.nHolding=c[0];a.nTableWrapper=d[0];a.nTableReinsertBefore=a.nTable.nextSibling;for(var f=a.sDom.split(""),h,i,j,n,m,o,k=0;k")[0];n=f[k+1];if("'"==n||'"'==n){m="";for(o=2;f[k+o]!=n;)m+=f[k+o],o++;"H"==m?m=b.sJUIHeader: 29 | "F"==m&&(m=b.sJUIFooter);-1!=m.indexOf(".")?(n=m.split("."),j.id=n[0].substr(1,n[0].length-1),j.className=n[1]):"#"==m.charAt(0)?j.id=m.substr(1,m.length-1):j.className=m;k+=o}d.append(j);d=g(j)}else if(">"==i)d=d.parent();else if("l"==i&&e.bPaginate&&e.bLengthChange)h=mb(a);else if("f"==i&&e.bFilter)h=nb(a);else if("r"==i&&e.bProcessing)h=ob(a);else if("t"==i)h=pb(a);else if("i"==i&&e.bInfo)h=qb(a);else if("p"==i&&e.bPaginate)h=rb(a);else if(0!==p.ext.feature.length){j=p.ext.feature;o=0;for(n=j.length;o< 30 | n;o++)if(i==j[o].cFeature){h=j[o].fnInit(a);break}}h&&(j=a.aanFeatures,j[i]||(j[i]=[]),j[i].push(h),d.append(h))}c.replaceWith(d)}function da(a,b){var c=g(b).children("tr"),e,d,f,h,i,j,n,m,o,k;a.splice(0,a.length);f=0;for(j=c.length;f',i=e.sSearch,i=i.match(/_INPUT_/)?i.replace("_INPUT_",h):i+h,b=g("
    ",{id:!f.f?c+"_filter":null,"class":b.sFilter}).append(g("
    ").addClass(b.sLength);a.aanFeatures.l||(j[0].id=c+"_length");j.children().append(a.oLanguage.sLengthMenu.replace("_MENU_",d[0].outerHTML));g("select",j).val(a._iDisplayLength).bind("change.DT", 47 | function(){Qa(a,g(this).val());L(a)});g(a.nTable).bind("length.dt.DT",function(b,c,f){a===c&&g("select",j).val(f)});return j[0]}function rb(a){var b=a.sPaginationType,c=p.ext.pager[b],e="function"===typeof c,d=function(a){L(a)},b=g("
    ").addClass(a.oClasses.sPaging+b)[0],f=a.aanFeatures;e||c.fnInit(a,b,d);f.p||(b.id=a.sTableId+"_paginate",a.aoDrawCallback.push({fn:function(a){if(e){var b=a._iDisplayStart,g=a._iDisplayLength,n=a.fnRecordsDisplay(),m=-1===g,b=m?0:Math.ceil(b/g),g=m?1:Math.ceil(n/ 48 | g),n=c(b,g),o,m=0;for(o=f.p.length;mf&&(e=0)):"first"==b?e=0:"previous"==b?(e=0<=d?e-d:0,0>e&&(e=0)):"next"==b?e+d",{id:!a.aanFeatures.r?a.sTableId+"_processing":null,"class":a.oClasses.sProcessing}).html(a.oLanguage.sProcessing).insertBefore(a.nTable)[0]}function B(a,b){a.oFeatures.bProcessing&&g(a.aanFeatures.r).css("display",b?"block":"none");u(a,null,"processing",[a,b])}function pb(a){var b=g(a.nTable);b.attr("role","grid");var c=a.oScroll;if(""===c.sX&&""===c.sY)return a.nTable;var e=c.sX,d=c.sY,f=a.oClasses,h=b.children("caption"),i=h.length?h[0]._captionSide:null, 50 | j=g(b[0].cloneNode(!1)),n=g(b[0].cloneNode(!1)),m=b.children("tfoot");c.sX&&"100%"===b.attr("width")&&b.removeAttr("width");m.length||(m=null);c=g("
    ",{"class":f.sScrollWrapper}).append(g("
    ",{"class":f.sScrollHead}).css({overflow:"hidden",position:"relative",border:0,width:e?!e?null:s(e):"100%"}).append(g("
    ",{"class":f.sScrollHeadInner}).css({"box-sizing":"content-box",width:c.sXInner||"100%"}).append(j.removeAttr("id").css("margin-left",0).append("top"===i?h:null).append(b.children("thead"))))).append(g("
    ", 51 | {"class":f.sScrollBody}).css({overflow:"auto",height:!d?null:s(d),width:!e?null:s(e)}).append(b));m&&c.append(g("
    ",{"class":f.sScrollFoot}).css({overflow:"hidden",border:0,width:e?!e?null:s(e):"100%"}).append(g("
    ",{"class":f.sScrollFootInner}).append(n.removeAttr("id").css("margin-left",0).append("bottom"===i?h:null).append(b.children("tfoot")))));var b=c.children(),o=b[0],f=b[1],k=m?b[2]:null;e&&g(f).scroll(function(){var a=this.scrollLeft;o.scrollLeft=a;m&&(k.scrollLeft=a)});a.nScrollHead= 52 | o;a.nScrollBody=f;a.nScrollFoot=k;a.aoDrawCallback.push({fn:Y,sName:"scrolling"});return c[0]}function Y(a){var b=a.oScroll,c=b.sX,e=b.sXInner,d=b.sY,f=b.iBarWidth,h=g(a.nScrollHead),i=h[0].style,j=h.children("div"),n=j[0].style,m=j.children("table"),j=a.nScrollBody,o=g(j),k=j.style,l=g(a.nScrollFoot).children("div"),p=l.children("table"),r=g(a.nTHead),q=g(a.nTable),t=q[0],N=t.style,J=a.nTFoot?g(a.nTFoot):null,u=a.oBrowser,w=u.bScrollOversize,y,v,x,K,z,A=[],B=[],C=[],D,E=function(a){a=a.style;a.paddingTop= 53 | "0";a.paddingBottom="0";a.borderTopWidth="0";a.borderBottomWidth="0";a.height=0};q.children("thead, tfoot").remove();z=r.clone().prependTo(q);y=r.find("tr");x=z.find("tr");z.find("th, td").removeAttr("tabindex");J&&(K=J.clone().prependTo(q),v=J.find("tr"),K=K.find("tr"));c||(k.width="100%",h[0].style.width="100%");g.each(pa(a,z),function(b,c){D=ka(a,b);c.style.width=a.aoColumns[D].sWidth});J&&F(function(a){a.style.width=""},K);b.bCollapse&&""!==d&&(k.height=o[0].offsetHeight+r[0].offsetHeight+"px"); 54 | h=q.outerWidth();if(""===c){if(N.width="100%",w&&(q.find("tbody").height()>j.offsetHeight||"scroll"==o.css("overflow-y")))N.width=s(q.outerWidth()-f)}else""!==e?N.width=s(e):h==o.width()&&o.height()h-f&&(N.width=s(h))):N.width=s(h);h=q.outerWidth();F(E,x);F(function(a){C.push(a.innerHTML);A.push(s(g(a).css("width")))},x);F(function(a,b){a.style.width=A[b]},y);g(x).height(0);J&&(F(E,K),F(function(a){B.push(s(g(a).css("width")))},K),F(function(a,b){a.style.width= 55 | B[b]},v),g(K).height(0));F(function(a,b){a.innerHTML='
    '+C[b]+"
    ";a.style.width=A[b]},x);J&&F(function(a,b){a.innerHTML="";a.style.width=B[b]},K);if(q.outerWidth()j.offsetHeight||"scroll"==o.css("overflow-y")?h+f:h;if(w&&(j.scrollHeight>j.offsetHeight||"scroll"==o.css("overflow-y")))N.width=s(v-f);(""===c||""!==e)&&R(a,1,"Possible column misalignment",6)}else v="100%";k.width=s(v);i.width=s(v);J&&(a.nScrollFoot.style.width= 56 | s(v));!d&&w&&(k.height=s(t.offsetHeight+f));d&&b.bCollapse&&(k.height=s(d),b=c&&t.offsetWidth>j.offsetWidth?f:0,t.offsetHeightj.clientHeight||"scroll"==o.css("overflow-y");u="padding"+(u.bScrollbarLeft?"Left":"Right");n[u]=m?f+"px":"0px";J&&(p[0].style.width=s(b),l[0].style.width=s(b),l[0].style[u]=m?f+"px":"0px");o.scroll();if((a.bSorted||a.bFiltered)&&!a._drawHold)j.scrollTop=0}function F(a, 57 | b,c){for(var e=0,d=0,f=b.length,h,g;d"));i.find("tfoot th, tfoot td").css("width","");var p=i.find("tbody tr"),j=pa(a,i.find("thead")[0]);for(k=0;k").css("width",s(a)).appendTo(b||P.body),e=c[0].offsetWidth;c.remove();return e}function Eb(a,b){var c=a.oScroll;if(c.sX||c.sY)c=!c.sX?c.iBarWidth:0,b.style.width=s(g(b).outerWidth()-c)}function Db(a,b){var c=Fb(a,b);if(0>c)return null; 61 | var e=a.aoData[c];return!e.nTr?g("").html(v(a,c,b,"display"))[0]:e.anCells[b]}function Fb(a,b){for(var c,e=-1,d=-1,f=0,h=a.aoData.length;fe&&(e=c.length,d=f);return d}function s(a){return null===a?"0px":"number"==typeof a?0>a?"0px":a+"px":a.match(/\d$/)?a+"px":a}function Gb(){if(!p.__scrollbarWidth){var a=g("

    ").css({width:"100%",height:200,padding:0})[0],b=g("

    ").css({position:"absolute",top:0,left:0,width:200,height:150,padding:0, 62 | overflow:"hidden",visibility:"hidden"}).append(a).appendTo("body"),c=a.offsetWidth;b.css("overflow","scroll");a=a.offsetWidth;c===a&&(a=b[0].clientWidth);b.remove();p.__scrollbarWidth=c-a}return p.__scrollbarWidth}function T(a){var b,c,e=[],d=a.aoColumns,f,h,i,j;b=a.aaSortingFixed;c=g.isPlainObject(b);var n=[];f=function(a){a.length&&!g.isArray(a[0])?n.push(a):n.push.apply(n,a)};g.isArray(b)&&f(b);c&&b.pre&&f(b.pre);f(a.aaSorting);c&&b.post&&f(b.post);for(a=0;ad?1:0,0!==c)return"asc"===g.dir?c:-c;c=e[a];d=e[b];return cd?1:0}):j.sort(function(a,b){var c,h,g,i,j=n.length,l=f[a]._aSortData,p=f[b]._aSortData;for(g=0;gh?1:0})}a.bSorted=!0}function Ib(a){for(var b,c,e=a.aoColumns,d=T(a),a=a.oLanguage.oAria,f=0,h=e.length;f/g,"");var j=c.nTh;j.removeAttribute("aria-sort");c.bSortable&&(0d?d+1:3));d=0;for(f=e.length;dd?d+1:3))}a.aLastSort=e}function Hb(a,b){var c=a.aoColumns[b],e=p.ext.order[c.sSortDataType],d;e&&(d=e.call(a.oInstance,a,b,$(a,b)));for(var f,h=p.ext.type.order[c.sType+"-pre"],g=0,j=a.aoData.length;g< 68 | j;g++)if(c=a.aoData[g],c._aSortData||(c._aSortData=[]),!c._aSortData[b]||e)f=e?d[g]:v(a,g,b,"sort"),c._aSortData[b]=h?h(f):f}function xa(a){if(a.oFeatures.bStateSave&&!a.bDestroying){var b={time:+new Date,start:a._iDisplayStart,length:a._iDisplayLength,order:g.extend(!0,[],a.aaSorting),search:yb(a.oPreviousSearch),columns:g.map(a.aoColumns,function(b,e){return{visible:b.bVisible,search:yb(a.aoPreSearchCols[e])}})};u(a,"aoStateSaveParams","stateSaveParams",[a,b]);a.oSavedState=b;a.fnStateSaveCallback.call(a.oInstance, 69 | a,b)}}function Jb(a){var b,c,e=a.aoColumns;if(a.oFeatures.bStateSave){var d=a.fnStateLoadCallback.call(a.oInstance,a);if(d&&d.time&&(b=u(a,"aoStateLoadParams","stateLoadParams",[a,d]),-1===g.inArray(!1,b)&&(b=a.iStateDuration,!(0=e.length?[0,c[1]]:c)});g.extend(a.oPreviousSearch, 70 | zb(d.search));b=0;for(c=d.columns.length;b=c&&(b=c-e);b-=b%e;if(-1===e||0>b)b=0;a._iDisplayStart=b}function Oa(a,b){var c=a.renderer,e=p.ext.renderer[b];return g.isPlainObject(c)&& 73 | c[b]?e[c[b]]||e._:"string"===typeof c?e[c]||e._:e._}function A(a){return a.oFeatures.bServerSide?"ssp":a.ajax||a.sAjaxSource?"ajax":"dom"}function Va(a,b){var c=[],c=Lb.numbers_length,e=Math.floor(c/2);b<=c?c=U(0,b):a<=e?(c=U(0,c-2),c.push("ellipsis"),c.push(b-1)):(a>=b-1-e?c=U(b-(c-2),b):(c=U(a-1,a+2),c.push("ellipsis"),c.push(b-1)),c.splice(0,0,"ellipsis"),c.splice(0,0,0));c.DT_el="span";return c}function cb(a){g.each({num:function(b){return za(b,a)},"num-fmt":function(b){return za(b,a,Wa)},"html-num":function(b){return za(b, 74 | a,Aa)},"html-num-fmt":function(b){return za(b,a,Aa,Wa)}},function(b,c){w.type.order[b+a+"-pre"]=c;b.match(/^html\-/)&&(w.type.search[b+a]=w.type.search.html)})}function Mb(a){return function(){var b=[ya(this[p.ext.iApiIndex])].concat(Array.prototype.slice.call(arguments));return p.ext.internal[a].apply(this,b)}}var p,w,q,r,t,Xa={},Nb=/[\r\n]/g,Aa=/<.*?>/g,$b=/^[\w\+\-]/,ac=/[\w\+\-]$/,Xb=RegExp("(\\/|\\.|\\*|\\+|\\?|\\||\\(|\\)|\\[|\\]|\\{|\\}|\\\\|\\$|\\^|\\-)","g"),Wa=/[',$\u00a3\u20ac\u00a5%\u2009\u202F]/g, 75 | H=function(a){return!a||!0===a||"-"===a?!0:!1},Ob=function(a){var b=parseInt(a,10);return!isNaN(b)&&isFinite(a)?b:null},Pb=function(a,b){Xa[b]||(Xa[b]=RegExp(ua(b),"g"));return"string"===typeof a&&"."!==b?a.replace(/\./g,"").replace(Xa[b],"."):a},Ya=function(a,b,c){var e="string"===typeof a;b&&e&&(a=Pb(a,b));c&&e&&(a=a.replace(Wa,""));return H(a)||!isNaN(parseFloat(a))&&isFinite(a)},Qb=function(a,b,c){return H(a)?!0:!(H(a)||"string"===typeof a)?null:Ya(a.replace(Aa,""),b,c)?!0:null},C=function(a, 76 | b,c){var e=[],d=0,f=a.length;if(c!==l)for(;d")[0],Yb=va.textContent!==l,Zb=/<.*?>/g;p=function(a){this.$=function(a,b){return this.api(!0).$(a,b)};this._=function(a,b){return this.api(!0).rows(a,b).data()};this.api=function(a){return a?new q(ya(this[w.iApiIndex])):new q(this)};this.fnAddData=function(a,b){var c=this.api(!0),e=g.isArray(a)&&(g.isArray(a[0])||g.isPlainObject(a[0]))? 78 | c.rows.add(a):c.row.add(a);(b===l||b)&&c.draw();return e.flatten().toArray()};this.fnAdjustColumnSizing=function(a){var b=this.api(!0).columns.adjust(),c=b.settings()[0],e=c.oScroll;a===l||a?b.draw(!1):(""!==e.sX||""!==e.sY)&&Y(c)};this.fnClearTable=function(a){var b=this.api(!0).clear();(a===l||a)&&b.draw()};this.fnClose=function(a){this.api(!0).row(a).child.hide()};this.fnDeleteRow=function(a,b,c){var e=this.api(!0),a=e.rows(a),d=a.settings()[0],g=d.aoData[a[0][0]];a.remove();b&&b.call(this,d,g); 79 | (c===l||c)&&e.draw();return g};this.fnDestroy=function(a){this.api(!0).destroy(a)};this.fnDraw=function(a){this.api(!0).draw(!a)};this.fnFilter=function(a,b,c,e,d,g){d=this.api(!0);null===b||b===l?d.search(a,c,e,g):d.column(b).search(a,c,e,g);d.draw()};this.fnGetData=function(a,b){var c=this.api(!0);if(a!==l){var e=a.nodeName?a.nodeName.toLowerCase():"";return b!==l||"td"==e||"th"==e?c.cell(a,b).data():c.row(a).data()||null}return c.data().toArray()};this.fnGetNodes=function(a){var b=this.api(!0); 80 | return a!==l?b.row(a).node():b.rows().nodes().flatten().toArray()};this.fnGetPosition=function(a){var b=this.api(!0),c=a.nodeName.toUpperCase();return"TR"==c?b.row(a).index():"TD"==c||"TH"==c?(a=b.cell(a).index(),[a.row,a.columnVisible,a.column]):null};this.fnIsOpen=function(a){return this.api(!0).row(a).child.isShown()};this.fnOpen=function(a,b,c){return this.api(!0).row(a).child(b,c).show().child()[0]};this.fnPageChange=function(a,b){var c=this.api(!0).page(a);(b===l||b)&&c.draw(!1)};this.fnSetColumnVis= 81 | function(a,b,c){a=this.api(!0).column(a).visible(b);(c===l||c)&&a.columns.adjust().draw()};this.fnSettings=function(){return ya(this[w.iApiIndex])};this.fnSort=function(a){this.api(!0).order(a).draw()};this.fnSortListener=function(a,b,c){this.api(!0).order.listener(a,b,c)};this.fnUpdate=function(a,b,c,e,d){var g=this.api(!0);c===l||null===c?g.row(b).data(a):g.cell(b,c).data(a);(d===l||d)&&g.columns.adjust();(e===l||e)&&g.draw();return 0};this.fnVersionCheck=w.fnVersionCheck;var b=this,c=a===l,e=this.length; 82 | c&&(a={});this.oApi=this.internal=w.internal;for(var d in p.ext.internal)d&&(this[d]=Mb(d));this.each(function(){var d={},d=1t<"F"ip>'),k.renderer)? 87 | g.isPlainObject(k.renderer)&&!k.renderer.header&&(k.renderer.header="jqueryui"):k.renderer="jqueryui":g.extend(j,p.ext.classes,d.oClasses);g(this).addClass(j.sTable);if(""!==k.oScroll.sX||""!==k.oScroll.sY)k.oScroll.iBarWidth=Gb();!0===k.oScroll.sX&&(k.oScroll.sX="100%");k.iInitDisplayStart===l&&(k.iInitDisplayStart=d.iDisplayStart,k._iDisplayStart=d.iDisplayStart);null!==d.iDeferLoading&&(k.bDeferLoading=!0,h=g.isArray(d.iDeferLoading),k._iRecordsDisplay=h?d.iDeferLoading[0]:d.iDeferLoading,k._iRecordsTotal= 88 | h?d.iDeferLoading[1]:d.iDeferLoading);var r=k.oLanguage;g.extend(!0,r,d.oLanguage);""!==r.sUrl&&(g.ajax({dataType:"json",url:r.sUrl,success:function(a){O(a);G(m.oLanguage,a);g.extend(true,r,a);ga(k)},error:function(){ga(k)}}),n=!0);null===d.asStripeClasses&&(k.asStripeClasses=[j.sStripeOdd,j.sStripeEven]);var h=k.asStripeClasses,q=g("tbody tr:eq(0)",this);-1!==g.inArray(!0,g.map(h,function(a){return q.hasClass(a)}))&&(g("tbody tr",this).removeClass(h.join(" ")),k.asDestroyStripes=h.slice());var o= 89 | [],s,h=this.getElementsByTagName("thead");0!==h.length&&(da(k.aoHeader,h[0]),o=pa(k));if(null===d.aoColumns){s=[];h=0;for(i=o.length;h").appendTo(this));k.nTHead=i[0];i=g(this).children("tbody");0===i.length&&(i=g("").appendTo(this));k.nTBody=i[0];i=g(this).children("tfoot");if(0===i.length&&0").appendTo(this);0===i.length||0===i.children().length?g(this).addClass(j.sNoFooter): 92 | 0a?new q(b[a],this[a]):null},filter:function(a){var b=[];if(y.filter)b=y.filter.call(this,a,this);else for(var c=0,e=this.length;c").addClass(b);g("td",c).addClass(b).html(a)[0].colSpan=aa(e);d.push(c[0])}};if(g.isArray(a)||a instanceof g)for(var h=0,i=a.length;h=0?b:h.length+b];if(typeof a==="function"){var d=Ba(c,f);return g.map(h,function(b,f){return a(f,Vb(c,f,0,0,d),j[f])?f:null})}var k=typeof a==="string"?a.match(cc):"";if(k)switch(k[2]){case "visIdx":case "visible":b= 116 | parseInt(k[1],10);if(b<0){var l=g.map(h,function(a,b){return a.bVisible?b:null});return[l[l.length+b]]}return[ka(c,b)];case "name":return g.map(i,function(a,b){return a===k[1]?b:null})}else return g(j).filter(a).map(function(){return g.inArray(this,j)}).toArray()})},1);c.selector.cols=a;c.selector.opts=b;return c});t("columns().header()","column().header()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].nTh},1)});t("columns().footer()","column().footer()",function(){return this.iterator("column", 117 | function(a,b){return a.aoColumns[b].nTf},1)});t("columns().data()","column().data()",function(){return this.iterator("column-rows",Vb,1)});t("columns().dataSrc()","column().dataSrc()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].mData},1)});t("columns().cache()","column().cache()",function(a){return this.iterator("column-rows",function(b,c,e,d,f){return ha(b.aoData,f,"search"===a?"_aFilterData":"_aSortData",c)},1)});t("columns().nodes()","column().nodes()",function(){return this.iterator("column-rows", 118 | function(a,b,c,e,d){return ha(a.aoData,d,"anCells",b)},1)});t("columns().visible()","column().visible()",function(a,b){return this.iterator("column",function(c,e){if(a===l)return c.aoColumns[e].bVisible;var d=c.aoColumns,f=d[e],h=c.aoData,i,j,n;if(a!==l&&f.bVisible!==a){if(a){var m=g.inArray(!0,C(d,"bVisible"),e+1);i=0;for(j=h.length;ie;return!0};p.isDataTable=p.fnIsDataTable=function(a){var b=g(a).get(0),c=!1;g.each(p.settings,function(a,d){if(d.nTable===b||d.nScrollHead===b||d.nScrollFoot===b)c=!0});return c};p.tables=p.fnTables=function(a){return g.map(p.settings,function(b){if(!a||a&&g(b.nTable).is(":visible"))return b.nTable})};p.util={throttle:ta,escapeRegex:ua}; 128 | p.camelToHungarian=G;r("$()",function(a,b){var c=this.rows(b).nodes(),c=g(c);return g([].concat(c.filter(a).toArray(),c.find(a).toArray()))});g.each(["on","one","off"],function(a,b){r(b+"()",function(){var a=Array.prototype.slice.call(arguments);a[0].match(/\.dt\b/)||(a[0]+=".dt");var e=g(this.tables().nodes());e[b].apply(e,a);return this})});r("clear()",function(){return this.iterator("table",function(a){na(a)})});r("settings()",function(){return new q(this.context,this.context)});r("data()",function(){return this.iterator("table", 129 | function(a){return C(a.aoData,"_aData")}).flatten()});r("destroy()",function(a){a=a||!1;return this.iterator("table",function(b){var c=b.nTableWrapper.parentNode,e=b.oClasses,d=b.nTable,f=b.nTBody,h=b.nTHead,i=b.nTFoot,j=g(d),f=g(f),l=g(b.nTableWrapper),m=g.map(b.aoData,function(a){return a.nTr}),o;b.bDestroying=!0;u(b,"aoDestroyCallback","destroy",[b]);a||(new q(b)).columns().visible(!0);l.unbind(".DT").find(":not(tbody *)").unbind(".DT");g(Da).unbind(".DT-"+b.sInstance);d!=h.parentNode&&(j.children("thead").detach(), 130 | j.append(h));i&&d!=i.parentNode&&(j.children("tfoot").detach(),j.append(i));j.detach();l.detach();b.aaSorting=[];b.aaSortingFixed=[];wa(b);g(m).removeClass(b.asStripeClasses.join(" "));g("th, td",h).removeClass(e.sSortable+" "+e.sSortableAsc+" "+e.sSortableDesc+" "+e.sSortableNone);b.bJUI&&(g("th span."+e.sSortIcon+", td span."+e.sSortIcon,h).detach(),g("th, td",h).each(function(){var a=g("div."+e.sSortJUIWrapper,this);g(this).append(a.contents());a.detach()}));!a&&c&&c.insertBefore(d,b.nTableReinsertBefore); 131 | f.children().detach();f.append(m);j.css("width",b.sDestroyWidth).removeClass(e.sTable);(o=b.asDestroyStripes.length)&&f.children().each(function(a){g(this).addClass(b.asDestroyStripes[a%o])});c=g.inArray(b,p.settings);-1!==c&&p.settings.splice(c,1)})});p.version="1.10.4";p.settings=[];p.models={};p.models.oSearch={bCaseInsensitive:!0,sSearch:"",bRegex:!1,bSmart:!0};p.models.oRow={nTr:null,anCells:null,_aData:[],_aSortData:null,_aFilterData:null,_sFilterRow:null,_sRowStripe:"",src:null};p.models.oColumn= 132 | {idx:null,aDataSort:null,asSorting:null,bSearchable:null,bSortable:null,bVisible:null,_sManualType:null,_bAttrSrc:!1,fnCreatedCell:null,fnGetData:null,fnSetData:null,mData:null,mRender:null,nTh:null,nTf:null,sClass:null,sContentPadding:null,sDefaultContent:null,sName:null,sSortDataType:"std",sSortingClass:null,sSortingClassJUI:null,sTitle:null,sType:null,sWidth:null,sWidthOrig:null};p.defaults={aaData:null,aaSorting:[[0,"asc"]],aaSortingFixed:[],ajax:null,aLengthMenu:[10,25,50,100],aoColumns:null, 133 | aoColumnDefs:null,aoSearchCols:[],asStripeClasses:null,bAutoWidth:!0,bDeferRender:!1,bDestroy:!1,bFilter:!0,bInfo:!0,bJQueryUI:!1,bLengthChange:!0,bPaginate:!0,bProcessing:!1,bRetrieve:!1,bScrollCollapse:!1,bServerSide:!1,bSort:!0,bSortMulti:!0,bSortCellsTop:!1,bSortClasses:!0,bStateSave:!1,fnCreatedRow:null,fnDrawCallback:null,fnFooterCallback:null,fnFormatNumber:function(a){return a.toString().replace(/\B(?=(\d{3})+(?!\d))/g,this.oLanguage.sThousands)},fnHeaderCallback:null,fnInfoCallback:null, 134 | fnInitComplete:null,fnPreDrawCallback:null,fnRowCallback:null,fnServerData:null,fnServerParams:null,fnStateLoadCallback:function(a){try{return JSON.parse((-1===a.iStateDuration?sessionStorage:localStorage).getItem("DataTables_"+a.sInstance+"_"+location.pathname))}catch(b){}},fnStateLoadParams:null,fnStateLoaded:null,fnStateSaveCallback:function(a,b){try{(-1===a.iStateDuration?sessionStorage:localStorage).setItem("DataTables_"+a.sInstance+"_"+location.pathname,JSON.stringify(b))}catch(c){}},fnStateSaveParams:null, 135 | iStateDuration:7200,iDeferLoading:null,iDisplayLength:10,iDisplayStart:0,iTabIndex:0,oClasses:{},oLanguage:{oAria:{sSortAscending:": activate to sort column ascending",sSortDescending:": activate to sort column descending"},oPaginate:{sFirst:"First",sLast:"Last",sNext:"Next",sPrevious:"Previous"},sEmptyTable:"No data available in table",sInfo:"Showing _START_ to _END_ of _TOTAL_ entries",sInfoEmpty:"Showing 0 to 0 of 0 entries",sInfoFiltered:"(filtered from _MAX_ total entries)",sInfoPostFix:"",sDecimal:"", 136 | sThousands:",",sLengthMenu:"Show _MENU_ entries",sLoadingRecords:"Loading...",sProcessing:"Processing...",sSearch:"Search:",sSearchPlaceholder:"",sUrl:"",sZeroRecords:"No matching records found"},oSearch:g.extend({},p.models.oSearch),sAjaxDataProp:"data",sAjaxSource:null,sDom:"lfrtip",searchDelay:null,sPaginationType:"simple_numbers",sScrollX:"",sScrollXInner:"",sScrollY:"",sServerMethod:"GET",renderer:null};V(p.defaults);p.defaults.column={aDataSort:null,iDataSort:-1,asSorting:["asc","desc"],bSearchable:!0, 137 | bSortable:!0,bVisible:!0,fnCreatedCell:null,mData:null,mRender:null,sCellType:"td",sClass:"",sContentPadding:"",sDefaultContent:null,sName:"",sSortDataType:"std",sTitle:null,sType:null,sWidth:null};V(p.defaults.column);p.models.oSettings={oFeatures:{bAutoWidth:null,bDeferRender:null,bFilter:null,bInfo:null,bLengthChange:null,bPaginate:null,bProcessing:null,bServerSide:null,bSort:null,bSortMulti:null,bSortClasses:null,bStateSave:null},oScroll:{bCollapse:null,iBarWidth:0,sX:null,sXInner:null,sY:null}, 138 | oLanguage:{fnInfoCallback:null},oBrowser:{bScrollOversize:!1,bScrollbarLeft:!1},ajax:null,aanFeatures:[],aoData:[],aiDisplay:[],aiDisplayMaster:[],aoColumns:[],aoHeader:[],aoFooter:[],oPreviousSearch:{},aoPreSearchCols:[],aaSorting:null,aaSortingFixed:[],asStripeClasses:null,asDestroyStripes:[],sDestroyWidth:0,aoRowCallback:[],aoHeaderCallback:[],aoFooterCallback:[],aoDrawCallback:[],aoRowCreatedCallback:[],aoPreDrawCallback:[],aoInitComplete:[],aoStateSaveParams:[],aoStateLoadParams:[],aoStateLoaded:[], 139 | sTableId:"",nTable:null,nTHead:null,nTFoot:null,nTBody:null,nTableWrapper:null,bDeferLoading:!1,bInitialised:!1,aoOpenRows:[],sDom:null,searchDelay:null,sPaginationType:"two_button",iStateDuration:0,aoStateSave:[],aoStateLoad:[],oSavedState:null,oLoadedState:null,sAjaxSource:null,sAjaxDataProp:null,bAjaxDataGet:!0,jqXHR:null,json:l,oAjaxData:l,fnServerData:null,aoServerParams:[],sServerMethod:null,fnFormatNumber:null,aLengthMenu:null,iDraw:0,bDrawing:!1,iDrawError:-1,_iDisplayLength:10,_iDisplayStart:0, 140 | _iRecordsTotal:0,_iRecordsDisplay:0,bJUI:null,oClasses:{},bFiltered:!1,bSorted:!1,bSortCellsTop:null,oInit:null,aoDestroyCallback:[],fnRecordsTotal:function(){return"ssp"==A(this)?1*this._iRecordsTotal:this.aiDisplayMaster.length},fnRecordsDisplay:function(){return"ssp"==A(this)?1*this._iRecordsDisplay:this.aiDisplay.length},fnDisplayEnd:function(){var a=this._iDisplayLength,b=this._iDisplayStart,c=b+a,e=this.aiDisplay.length,d=this.oFeatures,f=d.bPaginate;return d.bServerSide?!1===f||-1===a?b+e: 141 | Math.min(b+a,this._iRecordsDisplay):!f||c>e||-1===a?e:c},oInstance:null,sInstance:null,iTabIndex:0,nScrollHead:null,nScrollFoot:null,aLastSort:[],oPlugins:{}};p.ext=w={classes:{},errMode:"alert",feature:[],search:[],internal:{},legacy:{ajax:null},pager:{},renderer:{pageButton:{},header:{}},order:{},type:{detect:[],search:{},order:{}},_unique:0,fnVersionCheck:p.fnVersionCheck,iApiIndex:0,oJUIClasses:{},sVersion:p.version};g.extend(w,{afnFiltering:w.search,aTypes:w.type.detect,ofnSearch:w.type.search, 142 | oSort:w.type.order,afnSortData:w.order,aoFeatures:w.feature,oApi:w.internal,oStdClasses:w.classes,oPagination:w.pager});g.extend(p.ext.classes,{sTable:"dataTable",sNoFooter:"no-footer",sPageButton:"paginate_button",sPageButtonActive:"current",sPageButtonDisabled:"disabled",sStripeOdd:"odd",sStripeEven:"even",sRowEmpty:"dataTables_empty",sWrapper:"dataTables_wrapper",sFilter:"dataTables_filter",sInfo:"dataTables_info",sPaging:"dataTables_paginate paging_",sLength:"dataTables_length",sProcessing:"dataTables_processing", 143 | sSortAsc:"sorting_asc",sSortDesc:"sorting_desc",sSortable:"sorting",sSortableAsc:"sorting_asc_disabled",sSortableDesc:"sorting_desc_disabled",sSortableNone:"sorting_disabled",sSortColumn:"sorting_",sFilterInput:"",sLengthSelect:"",sScrollWrapper:"dataTables_scroll",sScrollHead:"dataTables_scrollHead",sScrollHeadInner:"dataTables_scrollHeadInner",sScrollBody:"dataTables_scrollBody",sScrollFoot:"dataTables_scrollFoot",sScrollFootInner:"dataTables_scrollFootInner",sHeaderTH:"",sFooterTH:"",sSortJUIAsc:"", 144 | sSortJUIDesc:"",sSortJUI:"",sSortJUIAscAllowed:"",sSortJUIDescAllowed:"",sSortJUIWrapper:"",sSortIcon:"",sJUIHeader:"",sJUIFooter:""});var Ca="",Ca="",E=Ca+"ui-state-default",ia=Ca+"css_right ui-icon ui-icon-",Wb=Ca+"fg-toolbar ui-toolbar ui-widget-header ui-helper-clearfix";g.extend(p.ext.oJUIClasses,p.ext.classes,{sPageButton:"fg-button ui-button "+E,sPageButtonActive:"ui-state-disabled",sPageButtonDisabled:"ui-state-disabled",sPaging:"dataTables_paginate fg-buttonset ui-buttonset fg-buttonset-multi ui-buttonset-multi paging_", 145 | sSortAsc:E+" sorting_asc",sSortDesc:E+" sorting_desc",sSortable:E+" sorting",sSortableAsc:E+" sorting_asc_disabled",sSortableDesc:E+" sorting_desc_disabled",sSortableNone:E+" sorting_disabled",sSortJUIAsc:ia+"triangle-1-n",sSortJUIDesc:ia+"triangle-1-s",sSortJUI:ia+"carat-2-n-s",sSortJUIAscAllowed:ia+"carat-1-n",sSortJUIDescAllowed:ia+"carat-1-s",sSortJUIWrapper:"DataTables_sort_wrapper",sSortIcon:"DataTables_sort_icon",sScrollHead:"dataTables_scrollHead "+E,sScrollFoot:"dataTables_scrollFoot "+E, 146 | sHeaderTH:E,sFooterTH:E,sJUIHeader:Wb+" ui-corner-tl ui-corner-tr",sJUIFooter:Wb+" ui-corner-bl ui-corner-br"});var Lb=p.ext.pager;g.extend(Lb,{simple:function(){return["previous","next"]},full:function(){return["first","previous","next","last"]},simple_numbers:function(a,b){return["previous",Va(a,b),"next"]},full_numbers:function(a,b){return["first","previous",Va(a,b),"next","last"]},_numbers:Va,numbers_length:7});g.extend(!0,p.ext.renderer,{pageButton:{_:function(a,b,c,e,d,f){var h=a.oClasses,i= 147 | a.oLanguage.oPaginate,j,l,m=0,o=function(b,e){var k,p,r,q,s=function(b){Sa(a,b.data.action,true)};k=0;for(p=e.length;k").appendTo(b);o(r,q)}else{l=j="";switch(q){case "ellipsis":b.append("");break;case "first":j=i.sFirst;l=q+(d>0?"":" "+h.sPageButtonDisabled);break;case "previous":j=i.sPrevious;l=q+(d>0?"":" "+h.sPageButtonDisabled);break;case "next":j=i.sNext;l=q+(d",{"class":h.sPageButton+" "+l,"aria-controls":a.sTableId,"data-dt-idx":m,tabindex:a.iTabIndex,id:c===0&&typeof q==="string"?a.sTableId+"_"+q:null}).html(j).appendTo(b);Ua(r,{action:q},s);m++}}}};try{var k=g(P.activeElement).data("dt-idx");o(g(b).empty(),e);k!==null&&g(b).find("[data-dt-idx="+k+"]").focus()}catch(p){}}}});g.extend(p.ext.type.detect,[function(a,b){var c=b.oLanguage.sDecimal; 149 | return Ya(a,c)?"num"+c:null},function(a){if(a&&!(a instanceof Date)&&(!$b.test(a)||!ac.test(a)))return null;var b=Date.parse(a);return null!==b&&!isNaN(b)||H(a)?"date":null},function(a,b){var c=b.oLanguage.sDecimal;return Ya(a,c,!0)?"num-fmt"+c:null},function(a,b){var c=b.oLanguage.sDecimal;return Qb(a,c)?"html-num"+c:null},function(a,b){var c=b.oLanguage.sDecimal;return Qb(a,c,!0)?"html-num-fmt"+c:null},function(a){return H(a)||"string"===typeof a&&-1!==a.indexOf("<")?"html":null}]);g.extend(p.ext.type.search, 150 | {html:function(a){return H(a)?a:"string"===typeof a?a.replace(Nb," ").replace(Aa,""):""},string:function(a){return H(a)?a:"string"===typeof a?a.replace(Nb," "):a}});var za=function(a,b,c,e){if(0!==a&&(!a||"-"===a))return-Infinity;b&&(a=Pb(a,b));a.replace&&(c&&(a=a.replace(c,"")),e&&(a=a.replace(e,"")));return 1*a};g.extend(w.type.order,{"date-pre":function(a){return Date.parse(a)||0},"html-pre":function(a){return H(a)?"":a.replace?a.replace(/<.*?>/g,"").toLowerCase():a+""},"string-pre":function(a){return H(a)? 151 | "":"string"===typeof a?a.toLowerCase():!a.toString?"":a.toString()},"string-asc":function(a,b){return ab?1:0},"string-desc":function(a,b){return ab?-1:0}});cb("");g.extend(!0,p.ext.renderer,{header:{_:function(a,b,c,e){g(a.nTable).on("order.dt.DT",function(d,f,h,g){if(a===f){d=c.idx;b.removeClass(c.sSortingClass+" "+e.sSortAsc+" "+e.sSortDesc).addClass(g[d]=="asc"?e.sSortAsc:g[d]=="desc"?e.sSortDesc:c.sSortingClass)}})},jqueryui:function(a,b,c,e){g("
    ").addClass(e.sSortJUIWrapper).append(b.contents()).append(g("").addClass(e.sSortIcon+ 152 | " "+c.sSortingClassJUI)).appendTo(b);g(a.nTable).on("order.dt.DT",function(d,f,g,i){if(a===f){d=c.idx;b.removeClass(e.sSortAsc+" "+e.sSortDesc).addClass(i[d]=="asc"?e.sSortAsc:i[d]=="desc"?e.sSortDesc:c.sSortingClass);b.find("span."+e.sSortIcon).removeClass(e.sSortJUIAsc+" "+e.sSortJUIDesc+" "+e.sSortJUI+" "+e.sSortJUIAscAllowed+" "+e.sSortJUIDescAllowed).addClass(i[d]=="asc"?e.sSortJUIAsc:i[d]=="desc"?e.sSortJUIDesc:c.sSortingClassJUI)}})}}});p.render={number:function(a,b,c,e){return{display:function(d){var f= 153 | 0>d?"-":"",d=Math.abs(parseFloat(d)),g=parseInt(d,10),d=c?b+(d-g).toFixed(c).substring(2):"";return f+(e||"")+g.toString().replace(/\B(?=(\d{3})+(?!\d))/g,a)+d}}}};g.extend(p.ext.internal,{_fnExternApiFunc:Mb,_fnBuildAjax:qa,_fnAjaxUpdate:jb,_fnAjaxParameters:sb,_fnAjaxUpdateDraw:tb,_fnAjaxDataSrc:ra,_fnAddColumn:Ea,_fnColumnOptions:ja,_fnAdjustColumnSizing:X,_fnVisibleToColumnIndex:ka,_fnColumnIndexToVisible:$,_fnVisbleColumns:aa,_fnGetColumns:Z,_fnColumnTypes:Ga,_fnApplyColumnDefs:hb,_fnHungarianMap:V, 154 | _fnCamelToHungarian:G,_fnLanguageCompat:O,_fnBrowserDetect:fb,_fnAddData:I,_fnAddTr:la,_fnNodeToDataIndex:function(a,b){return b._DT_RowIndex!==l?b._DT_RowIndex:null},_fnNodeToColumnIndex:function(a,b,c){return g.inArray(c,a.aoData[b].anCells)},_fnGetCellData:v,_fnSetCellData:Ha,_fnSplitObjNotation:Ja,_fnGetObjectDataFn:W,_fnSetObjectDataFn:Q,_fnGetDataMaster:Ka,_fnClearTable:na,_fnDeleteIndex:oa,_fnInvalidate:ca,_fnGetRowElements:ma,_fnCreateTr:Ia,_fnBuildHead:ib,_fnDrawHead:ea,_fnDraw:L,_fnReDraw:M, 155 | _fnAddOptionsHtml:lb,_fnDetectHeader:da,_fnGetUniqueThs:pa,_fnFeatureHtmlFilter:nb,_fnFilterComplete:fa,_fnFilterCustom:wb,_fnFilterColumn:vb,_fnFilter:ub,_fnFilterCreateSearch:Pa,_fnEscapeRegex:ua,_fnFilterData:xb,_fnFeatureHtmlInfo:qb,_fnUpdateInfo:Ab,_fnInfoMacros:Bb,_fnInitialise:ga,_fnInitComplete:sa,_fnLengthChange:Qa,_fnFeatureHtmlLength:mb,_fnFeatureHtmlPaginate:rb,_fnPageChange:Sa,_fnFeatureHtmlProcessing:ob,_fnProcessingDisplay:B,_fnFeatureHtmlTable:pb,_fnScrollDraw:Y,_fnApplyToChildren:F, 156 | _fnCalculateColumnWidths:Fa,_fnThrottle:ta,_fnConvertToWidth:Cb,_fnScrollingWidthAdjust:Eb,_fnGetWidestNode:Db,_fnGetMaxLenString:Fb,_fnStringToCss:s,_fnScrollBarWidth:Gb,_fnSortFlatten:T,_fnSort:kb,_fnSortAria:Ib,_fnSortListener:Ta,_fnSortAttachListener:Na,_fnSortingClasses:wa,_fnSortData:Hb,_fnSaveState:xa,_fnLoadState:Jb,_fnSettingsFromNode:ya,_fnLog:R,_fnMap:D,_fnBindAction:Ua,_fnCallbackReg:x,_fnCallbackFire:u,_fnLengthOverflow:Ra,_fnRenderer:Oa,_fnDataSource:A,_fnRowAttributes:La,_fnCalculateEnd:function(){}}); 157 | g.fn.dataTable=p;g.fn.dataTableSettings=p.settings;g.fn.dataTableExt=p.ext;g.fn.DataTable=function(a){return g(this).dataTable(a).api()};g.each(p,function(a,b){g.fn.DataTable[a]=b});return g.fn.dataTable};"function"===typeof define&&define.amd?define("datatables",["jquery"],O):"object"===typeof exports?O(require("jquery")):jQuery&&!jQuery.fn.dataTable&&O(jQuery)})(window,document); 158 | -------------------------------------------------------------------------------- /js/nmt-plugins-screen.js: -------------------------------------------------------------------------------- 1 | jQuery(document).ready(function($) { 2 | $('.disable').parents('.inactive').removeClass('inactive').addClass('enabled'); 3 | var selectbox = document.getElementById('bulk-action-selector-top'); 4 | text = '' + 5 | '' + 6 | '' + 7 | '' + 8 | '' + 9 | '' + 10 | ''; 11 | $(selectbox).html(text); 12 | }); -------------------------------------------------------------------------------- /js/nmt-site-info.js: -------------------------------------------------------------------------------- 1 | jQuery(document).ready(function( $ ) { 2 | $( ".progress" ).progressbar({ 3 | value: 0 4 | }); 5 | 6 | var action = 'build_site_info'; 7 | if ( typeof data == "undefined" ) { 8 | var data = { n: 0 }; 9 | } 10 | 11 | $('#site-info-form').submit(function() { 12 | $(this).hide(); 13 | $(".progress").show(); 14 | ajax_call(data); 15 | event.preventDefault(); 16 | }); 17 | 18 | function ajax_call(data) { 19 | data.action = action; 20 | $.ajax({ 21 | type: 'POST', 22 | url: ajaxurl, 23 | data: data, 24 | dataType: 'json', 25 | success: function (data) { 26 | completion_check(data); 27 | }, 28 | error: function (jqXHR, textStatus, thrownError) { 29 | console.log(jqXHR); 30 | console.log(textStatus); 31 | console.log(thrownError); 32 | } 33 | }); 34 | } 35 | 36 | function completion_check(data) { 37 | percent = Math.round((data.n / data.total) * 100); 38 | $( ".progress" ).progressbar( "option", "value", percent ); 39 | $( ".percent" ).text(percent + '%'); 40 | if ( percent == 100 ) { 41 | location.reload(); 42 | } else { 43 | ajax_call(data); 44 | } 45 | } 46 | 47 | var datatable = document.getElementById("network-data"); 48 | if ( datatable ) { 49 | $.extend( $.fn.dataTableExt.oStdClasses, { 50 | "sSortAsc": "sorted asc", 51 | "sSortDesc": "sorted desc", 52 | "sSortable": "sortable desc", 53 | "sPagePrevious": "prev-page", 54 | "sPageNext": "next-page", 55 | "sPageLast": "last-page", 56 | "sPageFirst": "first-page", 57 | "sPageButtonStaticDisabled": "disabled", 58 | "sStripeEven": "alternate" 59 | } ); 60 | var oTable = $(datatable).dataTable( { 61 | "aLengthMenu": [[10, 25, 50, 100, 500, -1], [10, 25, 50, 100, 500, "All"]], 62 | "iDisplayLength": 10, 63 | "aaSorting": [[ 0, "asc" ]], 64 | "bAutoWidth": false, 65 | "sPaginationType": "full_numbers", 66 | "oSearch": {"sSearch": "", "bSmart": false}, 67 | "sDom": "<'tablenav top'<'left one-page displaying-num' i><'right searchbox'f><'clear'>r>t<'btm-controls tablenav'<'left' l><'tablenav-pages' p><'clear'>>", 68 | "aoColumns": [ 69 | // { "bSortable": false }, 70 | null, 71 | null, 72 | null, 73 | null, 74 | null, 75 | //null, 76 | { "bSortable": false } 77 | ], 78 | "oLanguage": { 79 | "oPaginate": { 80 | "sFirst": "«", 81 | "sLast": "»", 82 | "sNext": "›", 83 | "sPrevious": "‹" 84 | } 85 | } 86 | }); 87 | 88 | } 89 | 90 | Array.prototype.slice.call(wp_addons); 91 | $('#network-data_filter input').autocomplete({ 92 | source: wp_addons, 93 | select: function(event,ui) { this.value=ui.item.value; oTable.fnFilter( ui.item.value ); } 94 | }); 95 | 96 | }); 97 | -------------------------------------------------------------------------------- /js/nmt-site-plugins-tab.js: -------------------------------------------------------------------------------- 1 | jQuery(document).ready(function($) { 2 | $.urlParam = function(name){ 3 | var results = new RegExp('[\\?&]' + name + '=([^&#]*)').exec(window.location.href); 4 | if (results===null){ 5 | return null; 6 | } 7 | else{ 8 | return results[1] || 0; 9 | } 10 | }; 11 | var pluginsTab = 'Plugins'; 12 | $('.nav-tab-wrapper a:nth-child(3)').after(pluginsTab); 13 | }); -------------------------------------------------------------------------------- /languages/index.php: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 16 | 21 | 26 | 31 | 36 | 37 | 38 | 39 | 40 | deleted == 1 ) { 51 | $class .= ' site-deleted'; 52 | $status = 'Deleted'; 53 | } 54 | elseif ( $site->archived == 1 ) { 55 | $class .= ' site-archived'; 56 | $status .= 'Archived'; 57 | } 58 | elseif ( $site->spam == 1 ) { 59 | $class .= ' site-spammed'; 60 | $status = 'Spam'; 61 | } 62 | // Increment the counter 63 | $n++; 64 | ?> 65 | 66 | 67 | 68 | 102 | 103 | 104 | 110 | 124 | 125 | 126 | 127 |
    12 | 13 | ID 14 | 15 | 17 | 18 | Site 19 | 20 | 22 | 23 | Registered 24 | 25 | 27 | 28 | Last Updated 29 | 30 | 32 | 33 | Theme 34 | 35 | Active Plugins
    blog_id; ?> 69 | domain . $site->path ); ?> 70 | 73 |
    74 | Dashboard 75 | blog_id != 1) { 79 | if ( $site->deleted == 1 ) 80 | echo '' . __( 'Activate' ) . ''; 81 | else 82 | echo '' . __( 'Deactivate' ) . ''; 83 | echo $sep; 84 | if ( $site->archived == 1 ) 85 | echo '' . __( 'Unarchive' ) . ''; 86 | else 87 | echo '' . _x( 'Archive', 'verb; site' ) . ''; 88 | echo $sep; 89 | if ( $site->spam == 1 ) 90 | echo '' . _x( 'Not Spam', 'site' ) . ''; 91 | else 92 | echo '' . _x( 'Spam', 'site' ) . ''; 93 | echo $sep; 94 | if ( current_user_can( 'delete_site', $site->blog_id ) ) 95 | echo '' . __( 'Delete' ) . ''; 96 | echo $sep; 97 | } 98 | ?> 99 | Visit 100 |
    101 |
    registered ) ); ?>last_updated ) ); ?> 105 | active_theme] ) ? $installed_themes[ $site->active_theme ]->Name : $site->active_theme; 107 | echo $theme; 108 | ?> 109 | 111 | active_plugins ); 113 | $plugins = array(); 114 | if ( ! empty( $active_plugins ) ) : ?> 115 |
      116 | 118 |
    • 119 | 121 |
    122 | 123 |
    -------------------------------------------------------------------------------- /uninstall.php: -------------------------------------------------------------------------------- 1 |