From 71cd92c04d9e447ad7f74618be0ee2fedad5688d Mon Sep 17 00:00:00 2001 From: plegall Date: Mon, 10 Jun 2024 21:31:41 +0200 Subject: [PATCH] fixes #2166 add feature to send anonymous stats to piwigo.org --- admin/include/functions.php | 148 +++++++++++++++ admin/intro.php | 84 +-------- admin/maintenance_env.php | 56 ++---- include/config_default.inc.php | 6 + include/functions.inc.php | 335 +++++++++++++++++++++++++++++++++ include/page_tail.php | 2 + 6 files changed, 512 insertions(+), 119 deletions(-) diff --git a/admin/include/functions.php b/admin/include/functions.php index 1e19e09b3..38a6f11e5 100644 --- a/admin/include/functions.php +++ b/admin/include/functions.php @@ -3669,3 +3669,151 @@ function get_piwigo_news() return $news; } + +function get_graphics_library() +{ + global $conf; + + include_once(PHPWG_ROOT_PATH.'admin/include/image.class.php'); + + $library = pwg_image::get_library(); + + switch (pwg_image::get_library()) + { + case 'imagick': + $img = new Imagick(); + $version = $img->getVersion(); + if (preg_match('/ImageMagick \d+\.\d+\.\d+-?\d*/', $version['versionString'], $match)) + { + $library.= '/'.$match[0]; + } + break; + + case 'ext_imagick': + exec($conf['ext_imagick_dir'].'convert -version', $returnarray); + if (preg_match('/Version: ImageMagick (\d+\.\d+\.\d+-?\d*)/', $returnarray[0], $match)) + { + $library.= '/'.$match[1]; + } + break; + + case 'gd': + $gd_info = gd_info(); + $library.= '/'.@$gd_info['GD Version']; + break; + } + + return $library; +} + +function get_graphics_library_label() +{ + list($library_code, $library_version) = explode('/', get_graphics_library()); + + $label_for_lib = array( + 'imagick' => 'ImageMagick', + 'ext_imagick' => 'External ImageMagick', + 'gd' => 'GD', + ); + + return $label_for_lib[$library_code].' '.$library_version; +} + +function get_pwg_general_statitics() +{ + $stats = array(); + + $query = ' +SELECT COUNT(*) + FROM '.IMAGES_TABLE.' +;'; + list($stats['nb_photos']) = pwg_db_fetch_row(pwg_query($query)); + + $query = ' +SELECT COUNT(*) + FROM '.CATEGORIES_TABLE.' +;'; + list($stats['nb_categories']) = pwg_db_fetch_row(pwg_query($query)); + + $query = ' +SELECT COUNT(*) + FROM '.TAGS_TABLE.' +;'; + list($stats['nb_tags']) = pwg_db_fetch_row(pwg_query($query)); + + $query = ' +SELECT COUNT(*) + FROM '.IMAGE_TAG_TABLE.' +;'; + list($stats['nb_image_tag']) = pwg_db_fetch_row(pwg_query($query)); + + $query = ' +SELECT COUNT(*) + FROM '.USERS_TABLE.' +;'; + list($stats['nb_users']) = pwg_db_fetch_row(pwg_query($query)); + + $query = ' +SELECT + COUNT(*) + FROM '.USER_INFOS_TABLE.' + WHERE status IN (\'webmaster\', \'admin\') +;'; + list($stats['nb_admins']) = pwg_db_fetch_row(pwg_query($query)); + + $query = ' +SELECT COUNT(*) + FROM `'.GROUPS_TABLE.'` +;'; + list($stats['nb_groups']) = pwg_db_fetch_row(pwg_query($query)); + + $query = ' +SELECT COUNT(*) + FROM '.RATE_TABLE.' +;'; + list($stats['nb_rates']) = pwg_db_fetch_row(pwg_query($query)); + + $query = ' +SELECT + SUM(nb_pages) + FROM '.HISTORY_SUMMARY_TABLE.' + WHERE month IS NULL +;'; + list($stats['nb_views']) = pwg_db_fetch_row(pwg_query($query)); + + $query = ' +SELECT + SUM(filesize) + FROM '.IMAGES_TABLE.' +;'; + list($stats['disk_usage']) = pwg_db_fetch_row(pwg_query($query)); + + $query = ' +SELECT + COUNT(*), + SUM(filesize) + FROM '.IMAGE_FORMAT_TABLE.' +;'; + list($stats['nb_formats'], $stats['formats_disk_usage']) = pwg_db_fetch_row(pwg_query($query)); + + $stats['disk_usage'] += $stats['formats_disk_usage']; + + return $stats; +} + +function get_installation_date() +{ + $query = ' +SELECT + registration_date + FROM '.USER_INFOS_TABLE.' + WHERE user_id = 2 +;'; + $users = query2array($query); + if (count($users) > 0) + { + return $users[0]['registration_date']; + } + + return null; +} \ No newline at end of file diff --git a/admin/intro.php b/admin/intro.php index d06abcad6..4cfcb02b5 100644 --- a/admin/intro.php +++ b/admin/intro.php @@ -109,74 +109,10 @@ if ($conf['show_newsletter_subscription'] and userprefs_get_param('show_newslett } -$query = ' -SELECT COUNT(*) - FROM '.IMAGES_TABLE.' -;'; -list($nb_photos) = pwg_db_fetch_row(pwg_query($query)); - -$query = ' -SELECT COUNT(*) - FROM '.CATEGORIES_TABLE.' -;'; -list($nb_categories) = pwg_db_fetch_row(pwg_query($query)); - -$query = ' -SELECT COUNT(*) - FROM '.TAGS_TABLE.' -;'; -list($nb_tags) = pwg_db_fetch_row(pwg_query($query)); - -$query = ' -SELECT COUNT(*) - FROM '.IMAGE_TAG_TABLE.' -;'; -list($nb_image_tag) = pwg_db_fetch_row(pwg_query($query)); - -$query = ' -SELECT COUNT(*) - FROM '.USERS_TABLE.' -;'; -list($nb_users) = pwg_db_fetch_row(pwg_query($query)); - -$query = ' -SELECT COUNT(*) - FROM `'.GROUPS_TABLE.'` -;'; -list($nb_groups) = pwg_db_fetch_row(pwg_query($query)); - -$query = ' -SELECT COUNT(*) - FROM '.RATE_TABLE.' -;'; -list($nb_rates) = pwg_db_fetch_row(pwg_query($query)); - -$query = ' -SELECT - SUM(nb_pages) - FROM '.HISTORY_SUMMARY_TABLE.' - WHERE month IS NULL -;'; -list($nb_views) = pwg_db_fetch_row(pwg_query($query)); - -$query = ' -SELECT - SUM(filesize) - FROM '.IMAGES_TABLE.' -;'; -list($disk_usage) = pwg_db_fetch_row(pwg_query($query)); - -$query = ' -SELECT - SUM(filesize) - FROM '.IMAGE_FORMAT_TABLE.' -;'; -list($formats_disk_usage) = pwg_db_fetch_row(pwg_query($query)); - -$disk_usage+= $formats_disk_usage; +$stats = get_pwg_general_statitics(); $du_decimals = 1; -$du_gb = $disk_usage/(1024*1024); +$du_gb = $stats['disk_usage']/(1024*1024); if ($du_gb > 100) { $du_decimals = 0; @@ -184,14 +120,14 @@ if ($du_gb > 100) $template->assign( array( - 'NB_PHOTOS' => $nb_photos, - 'NB_ALBUMS' => $nb_categories, - 'NB_TAGS' => $nb_tags, - 'NB_IMAGE_TAG' => $nb_image_tag, - 'NB_USERS' => $nb_users, - 'NB_GROUPS' => $nb_groups, - 'NB_RATES' => $nb_rates, - 'NB_VIEWS' => number_format_human_readable($nb_views), + 'NB_PHOTOS' => $stats['nb_photos'], + 'NB_ALBUMS' => $stats['nb_categories'], + 'NB_TAGS' => $stats['nb_tags'], + 'NB_IMAGE_TAG' => $stats['nb_image_tag'], + 'NB_USERS' => $stats['nb_users'], + 'NB_GROUPS' => $stats['nb_groups'], + 'NB_RATES' => $stats['nb_rates'], + 'NB_VIEWS' => number_format_human_readable($stats['nb_views']), 'NB_PLUGINS' => count($pwg_loaded_plugins), 'STORAGE_USED' => str_replace(' ', ' ', l10n('%sGB', number_format($du_gb, $du_decimals))), 'U_QUICK_SYNC' => PHPWG_ROOT_PATH.'admin.php?page=site_update&site=1&quick_sync=1&pwg_token='.get_pwg_token(), diff --git a/admin/maintenance_env.php b/admin/maintenance_env.php index 484f4e779..2fd4a7440 100644 --- a/admin/maintenance_env.php +++ b/admin/maintenance_env.php @@ -301,33 +301,10 @@ $template->assign( ); // graphics library -switch (pwg_image::get_library()) +$graphics_library = get_graphics_library_label(); +if (!empty($graphics_library)) { - case 'imagick': - $library = 'ImageMagick'; - $img = new Imagick(); - $version = $img->getVersion(); - if (preg_match('/ImageMagick \d+\.\d+\.\d+-?\d*/', $version['versionString'], $match)) - { - $library = $match[0]; - } - $template->assign('GRAPHICS_LIBRARY', $library); - break; - - case 'ext_imagick': - $library = 'External ImageMagick'; - exec($conf['ext_imagick_dir'].'convert -version', $returnarray); - if (preg_match('/Version: ImageMagick (\d+\.\d+\.\d+-?\d*)/', $returnarray[0], $match)) - { - $library .= ' ' . $match[1]; - } - $template->assign('GRAPHICS_LIBRARY', $library); - break; - - case 'gd': - $gd_info = gd_info(); - $template->assign('GRAPHICS_LIBRARY', 'GD '.@$gd_info['GD Version']); - break; + $template->assign('GRAPHICS_LIBRARY', $graphics_library); } if ($conf['gallery_locked']) @@ -347,26 +324,15 @@ else ); } -$query = ' -SELECT - registration_date - FROM '.USER_INFOS_TABLE.' - WHERE user_id = 2 -;'; -$users = query2array($query); -if (count($users) > 0) +$installed_on = get_installation_date(); +if (!empty($installed_on)) { - $installed_on = $users[0]['registration_date']; - - if (!empty($installed_on)) - { - $template->assign( - array( - 'INSTALLED_ON' => format_date($installed_on, array('day', 'month', 'year')), - 'INSTALLED_SINCE' => time_since($installed_on, 'day'), - ) - ); - } + $template->assign( + array( + 'INSTALLED_ON' => format_date($installed_on, array('day', 'month', 'year')), + 'INSTALLED_SINCE' => time_since($installed_on, 'day'), + ) + ); } // +-----------------------------------------------------------------------+ diff --git a/include/config_default.inc.php b/include/config_default.inc.php index 8e68214a7..96f3dee36 100644 --- a/include/config_default.inc.php +++ b/include/config_default.inc.php @@ -273,6 +273,12 @@ $conf['update_notify_check_period'] = 24*60*60; // we send it again? 0 to disable. $conf['update_notify_reminder_period'] = 7*24*60*60; +// once a week, Piwigo *anonymously* sends technical data and general +// statistics, such as number of photos or list of plugins used. It helps +// piwigo.org to know better how Piwigo is used. This way developers can +// focus on features that matter most. +$conf['send_piwigo_infos'] = true; + // should the album description be displayed on all pages (value=true) or // only the first page (value=false) $conf['album_description_on_all_pages'] = false; diff --git a/include/functions.inc.php b/include/functions.inc.php index 4a2727eba..c5c98a51c 100644 --- a/include/functions.inc.php +++ b/include/functions.inc.php @@ -2412,4 +2412,339 @@ SELECT } } +/** + * Piwigo *anonymously* sends technical data and general statistics, such as number + * of photos or list of plugins used. It helps piwigo.org to know better how Piwigo + * is used. This way developers can focus on features that matter most. + * + * @since 15 + */ +function send_piwigo_infos() +{ + global $logger, $conf; + + $start_time = get_moment(); + + if (!$conf['send_piwigo_infos']) + { + return; + } + + $do_send = false; + if (isset($conf['send_piwigo_infos_last_notice'])) + { + if (strtotime($conf['send_piwigo_infos_last_notice']) < strtotime(conf_get_param('send_piwigo_infos_period', 7*24*60*60).' second ago')) + { + $do_send = true; + } + } + else + { + $do_send = true; + } + + if (!$do_send) + { + return; + } + + if (!pwg_is_dbconf_writeable()) + { + return; + } + + $exec_id = substr(sha1(random_bytes(1000)), 0, 8); + $logger->info('['.__FUNCTION__.'][exec='.$exec_id.'] starts now'); + + // we only want one send_piwigo_infos to run at once + if (isset($conf['send_piwigo_infos_running'])) + { + list($running_exec_id, $running_exec_start_time) = explode('-', $conf['send_piwigo_infos_running']); + if (time() - $running_exec_start_time > 60) + { + $logger->info('['.__FUNCTION__.'][exec='.$exec_id.'] exec='.$running_exec_id.', timeout stopped by another call to the function'); + conf_delete_param('send_piwigo_infos_running'); + } + } + + $query = ' +INSERT IGNORE + INTO '.CONFIG_TABLE.' + SET param="send_piwigo_infos_running" + , value="'.$exec_id.'-'.time().'" +;'; + pwg_query($query); + + list($send_piwigo_infos_running) = pwg_db_fetch_row(pwg_query('SELECT value FROM '.CONFIG_TABLE.' WHERE param = "send_piwigo_infos_running"')); + list($running_exec_id,) = explode('-', $send_piwigo_infos_running); + + if ($running_exec_id != $exec_id) + { + $logger->info('['.__FUNCTION__.'][exec='.$exec_id.'] skip'); + return; + } + $logger->info('['.__FUNCTION__.'][exec='.$exec_id.'] wins the race and gets the token!'); + + include_once(PHPWG_ROOT_PATH.'admin/include/functions.php'); + + list($db_current_date) = pwg_db_fetch_row(pwg_query('SELECT now();')); + + if (!isset($conf['send_piwigo_infos_origin_hash'])) + { + conf_update_param('send_piwigo_infos_origin_hash', sha1(random_bytes(1000)), true); + } + + $piwigo_infos = array( + 'origin_hash' => $conf['send_piwigo_infos_origin_hash'], + 'technical' => array( + 'php_version' => PHP_VERSION, + 'piwigo_version' => PHPWG_VERSION, + 'os_version' => PHP_OS, + 'db_version' => pwg_get_db_version(), + 'php_datetime' => date("Y-m-d H:i:s"), + 'db_datetime' => $db_current_date, + 'graphics_library' => get_graphics_library(), + ), + 'general_stats' => get_pwg_general_statitics(), + ); + + // convert disk_usage from kB to mB + $piwigo_infos['general_stats']['disk_usage'] = intval($piwigo_infos['general_stats']['disk_usage'] / 1024); + + $piwigo_infos['general_stats']['installed_on'] = get_installation_date(); + + // $conf['pem_plugins_category'] = 12; + // $conf['pem_themes_category'] = 10; + $url = PEM_URL . '/api/get_extension_list.php'; + if (fetchRemote($url, $result) and $pem_extensions = @unserialize($result)) + { + $official_exts = array(); + foreach ($pem_extensions as $eid => $ext) + { + if (!empty($ext['archive_root_dir'])) + { + @$official_exts[ $ext['idx_category'] ][ $ext['archive_root_dir'] ] = $eid; + } + } + } + else + { + $logger->info('['.__FUNCTION__.'][exec='.$exec_id.'] fetchRemote on '.$url.' has failed'); + } + + include_once(PHPWG_ROOT_PATH.'admin/include/plugins.class.php'); + $plugins = new plugins(); + $piwigo_infos['general_stats']['nb_private_plugins'] = 0; + $piwigo_infos['plugins'] = array(); + foreach ($plugins->db_plugins_by_id as $plugin) + { + if ('active' == $plugin['state']) + { + $eid = null; + if (isset($plugins->fs_plugins[ $plugin['id'] ])) + { + $uri = $plugins->fs_plugins[ $plugin['id'] ]['uri']; + if (preg_match('/eid=(\d+)/', $uri, $matches)) + { + $eid = $matches[1]; + } + } + + if (empty($eid)) + { + // let's search in the data fetched from PEM + $eid = $official_exts[ $conf['pem_plugins_category'] ][ $plugin['id'] ] ?? null; + } + + // we must exclude "private extensions". A private extension : + // + // * has no eid + // * OR has un unknown plugin_id among all "Archive root directory" in PEM + if (empty($eid)) + { + $piwigo_infos['general_stats']['nb_private_plugins']++; + continue; + } + + $piwigo_infos['plugins'][] = (empty($eid) ? 'null' : '#'.$eid).'/'.$plugin['id'].'/'.$plugin['version']; + } + } + + $piwigo_infos['general_stats']['nb_plugins'] = $piwigo_infos['general_stats']['nb_private_plugins'] + count($piwigo_infos['plugins']); + + include_once(PHPWG_ROOT_PATH.'admin/include/themes.class.php'); + $themes = new themes(); + $piwigo_infos['general_stats']['nb_private_themes'] = 0; + $piwigo_infos['themes'] = array(); + $private_themes = array(); + foreach ($themes->db_themes_by_id as $theme) + { + $theme['state'] = 'active'; + if ('active' == $theme['state']) + { + $eid = null; + if (isset($themes->fs_themes[ $theme['id'] ])) + { + $uri = $themes->fs_themes[ $theme['id'] ]['uri']; + if (preg_match('/eid=(\d+)/', $uri, $matches)) + { + $eid = $matches[1]; + } + } + + if (empty($eid)) + { + // let's search in the data fetched from PEM + $eid = $official_exts[ $conf['pem_themes_category'] ][ $theme['id'] ] ?? null; + } + + // we must exclude "private extensions". A private extension : + // + // * has no eid + // * OR has un unknown theme_id among all "Archive root directory" in PEM + if (empty($eid)) + { + $private_themes[ $theme['id'] ] = 1; + continue; + } + + $piwigo_infos['themes'][] = (empty($eid) ? 'null' : '#'.$eid).'/'.$theme['id'].'/'.$theme['version']; + } + } + + $piwigo_infos['general_stats']['nb_private_themes'] = count(array_keys($private_themes)); + $piwigo_infos['general_stats']['nb_themes'] = $piwigo_infos['general_stats']['nb_private_themes'] + count($piwigo_infos['themes']); + + $default_theme = get_default_theme(); + if (isset($private_themes[$default_theme])) + { + $default_theme = 'private theme'; + } + $piwigo_infos['general_stats']['default_theme'] = $default_theme; + + $piwigo_infos['themes_usage'] = array(); + $query = ' +SELECT + theme, + COUNT(*) AS theme_counter + FROM '.USER_INFOS_TABLE.' + GROUP BY theme + ORDER BY theme +;'; + $themes_used = query2array($query, 'theme', 'theme_counter'); + foreach ($themes_used as $theme_used => $counter) + { + if (isset($private_themes[$theme_used])) + { + $theme_used = 'private theme'; + } + + @$piwigo_infos['themes_usage'][$theme_used] += $counter; + } + + $piwigo_infos['general_stats']['default_language'] = get_default_language(); + + $query = ' +SELECT + language, + COUNT(*) AS language_counter + FROM '.USER_INFOS_TABLE.' + GROUP BY language + ORDER BY language +;'; + $piwigo_infos['languages_usage'] = query2array($query, 'language', 'language_counter'); + + $piwigo_infos['activities'] = array(); + $piwigo_infos['general_stats']['nb_activities'] = 0; + + $query = ' +SELECT + object, + action, + COUNT(*) AS counter + FROM '.ACTIVITY_TABLE.' + WHERE object != \'system\' + GROUP BY object, action +;'; + $activities = query2array($query); + foreach ($activities as $activity) + { + $piwigo_infos['general_stats']['nb_activities'] += $activity['counter']; + @$piwigo_infos['activities'][ $activity['object'] ][ $activity['action'] ] = $activity['counter']; + } + + $label_for_system_object_id = array( + 1 => 'core', + 2 => 'plugin', + 3 => 'theme', + ); + + $query = ' +SELECT + object, + object_id, + action, + COUNT(*) AS counter + FROM '.ACTIVITY_TABLE.' + WHERE object = \'system\' + GROUP BY object, object_id, action +;'; + $activities = query2array($query); + foreach ($activities as $activity) + { + @$piwigo_infos['activities'][ $activity['object'] ][ $label_for_system_object_id[ $activity['object_id'] ] ?? 'undefined' ][ $activity['action'] ] = $activity['counter']; + } + + $watermark = ImageStdParams::get_watermark(); + + $piwigo_infos['features'] = array( + 'use_watermark' => !empty($watermark->file) ? 'yes' : 'no', + ); + + $features = array( + 'activate_comments', + 'rate', + 'log', + 'history_guest', + 'history_admin', + ); + + foreach ($features as $feature) + { + $piwigo_infos['features'][$feature] = $conf[$feature] ? 'yes' : 'no'; + } + + $url = conf_get_param('send_piwigo_infos_update_url', PHPWG_URL).'/ws.php'; + + $get_data = array( + 'format' => 'php', + 'method' => 'porg.installs.update', + 'origin_hash' => $piwigo_infos['origin_hash'], + ); + + $post_data = array( + 'data' => json_encode($piwigo_infos) + ); + + if (!fetchRemote($url, $result, $get_data, $post_data)) + { + $logger->info('['.__FUNCTION__.'][exec='.$exec_id.'] fetchRemote on '.$url.' method=porg.installs.update has failed'); + + // let's fake a last_notice so that we only try 1 day later + $last_notice = isset($conf['send_piwigo_infos_last_notice']) ? strtotime($conf['send_piwigo_infos_last_notice']) : time(); + $last_notice += 24*60*60; + + conf_update_param('send_piwigo_infos_last_notice', date('c', $last_notice)); + } + else + { + conf_update_param('send_piwigo_infos_last_notice', date('c')); + } + + echo '
'; print_r($piwigo_infos); echo '
'; + + conf_delete_param('send_piwigo_infos_running'); + $logger->info('['.__FUNCTION__.'][exec='.$exec_id.'] executed in '.get_elapsed_time($start_time, get_moment())); +} + ?> diff --git a/include/page_tail.php b/include/page_tail.php index f7f8fa2d1..947091fe0 100644 --- a/include/page_tail.php +++ b/include/page_tail.php @@ -49,6 +49,8 @@ if ($conf['update_notify_check_period'] > 0) } } +send_piwigo_infos(); + //------------------------------------------------------------- generation time $debug_vars = array();