*/ public function collect() { $context = $this->context(); $data = array( 'core' => get_bloginfo('version'), 'php' => $this->phpVersion(), 'plugins' => $this->plugins($context), 'themes' => $this->themes($context), ); if ($context['multisite']) { $data['multisite'] = true; $data['network_sites'] = $context['sites']; $data['subsites_scanned'] = $context['scanned']; } return $data; } /** * A stable fingerprint of the payload, so the plugin and the server both skip no-op reports. * * @param array $inventory */ public function hash(array $inventory) { return md5((string) wp_json_encode($this->normalize($inventory))); } private function phpVersion() { return defined('PHP_VERSION') ? PHP_VERSION : phpversion(); } /** * Where a plugin or theme is active. On a single site this is just the one site. * * @return array */ private function context() { if (!is_multisite()) { $url = home_url('/'); $themes = array(get_stylesheet() => array($url)); $template = get_template(); if ($template !== get_stylesheet()) { $themes[$template][] = $url; } return array( 'multisite' => false, 'sites' => 1, 'scanned' => true, 'networkPlugins' => array(), 'sitePlugins' => $this->mapFiles((array) get_option('active_plugins', array()), $url), 'siteThemes' => $themes, ); } $networkPlugins = array_keys((array) get_site_option('active_sitewide_plugins', array())); $total = (int) get_sites(array('count' => true)); $cap = (int) apply_filters('ladon_multisite_site_cap', 100); $scanned = $total <= $cap; $sitePlugins = array(); $siteThemes = array(); if ($scanned) { foreach (get_sites(array('number' => $cap, 'fields' => 'ids')) as $blogId) { $url = get_home_url((int) $blogId, '/'); foreach ((array) get_blog_option((int) $blogId, 'active_plugins', array()) as $file) { $sitePlugins[(string) $file][] = $url; } $stylesheet = (string) get_blog_option((int) $blogId, 'stylesheet'); $template = (string) get_blog_option((int) $blogId, 'template'); if ($stylesheet !== '') { $siteThemes[$stylesheet][] = $url; } if ($template !== '' && $template !== $stylesheet) { $siteThemes[$template][] = $url; } } } return array( 'multisite' => true, 'sites' => $total, 'scanned' => $scanned, 'networkPlugins' => array_fill_keys(array_map('strval', $networkPlugins), true), 'sitePlugins' => $sitePlugins, 'siteThemes' => $siteThemes, ); } /** * @param array $files * @return array> */ private function mapFiles(array $files, $url) { $map = array(); foreach ($files as $file) { $map[(string) $file][] = $url; } return $map; } /** * @param array $context * @return array> */ private function plugins(array $context) { if (!function_exists('get_plugins')) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $updates = $this->updateVersions('update_plugins'); $out = array(); foreach (get_plugins() as $file => $data) { $file = (string) $file; $slug = $this->slug(strpos($file, '/') !== false ? dirname($file) : basename($file, '.php')); if ($slug === '') { continue; } $networkActive = isset($context['networkPlugins'][$file]); $sites = isset($context['sitePlugins'][$file]) ? $context['sitePlugins'][$file] : array(); $item = array( 'slug' => $slug, 'version' => isset($data['Version']) ? (string) $data['Version'] : '', 'active' => $networkActive || !empty($sites), ); $this->attachScope($item, $context, $networkActive, $sites); if (isset($updates[$file])) { $item['new_version'] = $updates[$file]; } $out[] = $item; } return $out; } /** * @param array $context * @return array> */ private function themes(array $context) { $updates = $this->updateVersions('update_themes'); $out = array(); foreach (wp_get_themes() as $stylesheet => $theme) { $stylesheet = (string) $stylesheet; $slug = $this->slug($stylesheet); if ($slug === '') { continue; } $sites = isset($context['siteThemes'][$stylesheet]) ? $context['siteThemes'][$stylesheet] : array(); $item = array( 'slug' => $slug, 'version' => (string) $theme->get('Version'), 'active' => !empty($sites), ); $this->attachScope($item, $context, false, $sites); if (isset($updates[$stylesheet])) { $item['new_version'] = $updates[$stylesheet]; } $out[] = $item; } return $out; } /** * Network-active means everywhere, so no list. A per-site activation on multisite lists the sites. * * @param array $item * @param array $context * @param array $sites */ private function attachScope(array &$item, array $context, $networkActive, array $sites) { if (!$context['multisite'] || $networkActive || empty($sites)) { return; } $sites = array_values(array_unique($sites)); sort($sites); $cap = (int) apply_filters('ladon_multisite_site_cap', 100); $item['active_sites'] = array_slice($sites, 0, $cap); } /** * @return array */ private function updateVersions($transient) { $data = get_site_transient($transient); if (!is_object($data) || empty($data->response) || !is_array($data->response)) { return array(); } $out = array(); foreach ($data->response as $key => $value) { if (is_object($value) && isset($value->new_version)) { $out[(string) $key] = (string) $value->new_version; } elseif (is_array($value) && isset($value['new_version'])) { $out[(string) $key] = (string) $value['new_version']; } } return $out; } private function slug($value) { return preg_replace('/[^A-Za-z0-9._-]/', '', (string) $value); } /** * @param array $inventory * @return array */ private function normalize(array $inventory) { $sort = function (array $items) { usort($items, function ($a, $b) { return strcmp($a['slug'], $b['slug']); }); return $items; }; $inventory['plugins'] = $sort($inventory['plugins']); $inventory['themes'] = $sort($inventory['themes']); return $inventory; } }