Issues (4868)

admin/inc/class.admin_statistics.inc.php (6 issues)

1
<?php
2
/**
3
 * EGgroupware admin - submit EGroupware usage statistic
4
 *
5
 * @link http://www.egroupware.org
6
 * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
7
 * @package admin
8
 * @copyright (c) 2009-18 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
9
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
10
 */
11
12
use EGroupware\Api;
13
use EGroupware\Api\Egw;
14
use EGroupware\Api\Etemplate;
15
16
/**
17
 * Submit statistical data to egroupware.org
18
 */
19
class admin_statistics
20
{
21
	const CONFIG_APP = 'admin';
22
	const CONFIG_LAST_SUBMIT = 'last_statistics_submit';
23
	const CONFIG_POSTPONE_SUBMIT = 'postpone_statistics_submit';
24
	const CONFIG_SUBMIT_ID = 'statistics_submit_id';
25
	const CONFIG_COUNTRY = 'country_submit';
26
	const CONFIG_USAGE_TYPE = 'usage_type_submit';
27
	const CONFIG_INSTALL_TYPE = 'install_type_submit';
28
29
	const SUBMIT_URL = 'https://www.egroupware.org/usage-statistic';
30
	const STATISTIC_URL = 'https://www.egroupware.org/usage-statistic';
31
32
	const SUBMISION_RATE = 2592000;	// 30 days
33
34
	/**
35
	 * Which methods of this class can be called as menuation
36
	 *
37
	 * @var array
38
	 */
39
	public $public_functions = array(
40
		'submit' => true,
41
	);
42
43
	/**
44
	 * Display and allow to submit statistical data
45
	 *
46
	 * @param array $_content =null
47
	 */
48
	public function submit($_content=null)
49
	{
50
		if (is_array($_content))
51
		{
52
			$config = new Api\Config(self::CONFIG_APP);
0 ignored issues
show
The assignment to $config is dead and can be removed.
Loading history...
53
			if ($_content['postpone'])
54
			{
55
				Api\Config::save_value(self::CONFIG_POSTPONE_SUBMIT,time()+$_content['postpone'],self::CONFIG_APP);
56
				$what = 'postpone';
57
			}
58
			elseif(!$_content['cancel'])
59
			{
60
				Api\Config::save_value(self::CONFIG_LAST_SUBMIT,time(),self::CONFIG_APP);
61
				Api\Config::save_value(self::CONFIG_SUBMIT_ID,empty($_content['submit_id']) ? '***none***' : $_content['submit_id'],self::CONFIG_APP);
62
				Api\Config::save_value(self::CONFIG_COUNTRY,empty($_content['country']) ? '***multinational***' : $_content['country'],self::CONFIG_APP);
63
				Api\Config::save_value(self::CONFIG_USAGE_TYPE,$_content['usage_type'],self::CONFIG_APP);
64
				Api\Config::save_value(self::CONFIG_INSTALL_TYPE,$_content['install_type'],self::CONFIG_APP);
65
				Api\Config::save_value(self::CONFIG_POSTPONE_SUBMIT,null,self::CONFIG_APP);	// remove evtl. postpone time
66
				$what = 'submitted';
67
			}
68
			Egw::redirect_link('/admin/index.php','ajax=true&statistics='.($what ? $what : 'canceled'),'admin');
69
		}
70
		$sel_options['usage_type'] = array(
0 ignored issues
show
Comprehensibility Best Practice introduced by
$sel_options was never initialized. Although not strictly required by PHP, it is generally a good practice to add $sel_options = array(); before regardless.
Loading history...
71
			'commercial'   => lang('Commercial: all sorts of companies'),
72
			'governmental' => lang('Governmental: incl. state or municipal authorities or services'),
73
			'educational' => lang('Educational: Universities, Schools, ...'),
74
			'non-profit'  => lang('Non profit: Clubs, associations, ...'),
75
			'personal'    => lang('Personal: eg. within a family'),
76
			'other'       => lang('Other'),
77
		);
78
		$sel_options['install_type'] = array(
79
			'docker'  => lang('Docker'),
80
			'package' => lang('RPM or Debian package'),
81
			'git'     => lang('Git clone'),
82
			'archive' => lang('Archive: zip or tar'),
83
			'other'   => lang('Other'),
84
		);
85
		$sel_options['postpone'] = array(
86
			//10 => '10 secs',
87
			3600 => lang('one hour'),
88
			2*3600 => lang('two hours'),
89
			24*3600 => lang('one day'),
90
			2*24*3600 => lang('two days'),
91
			7*24*3600 => lang('one week'),
92
			14*24*3600 => lang('two weeks'),
93
			30*24*3600 => lang('one month'),
94
			60*24*3600 => lang('two months'),
95
		);
96
		$config = Api\Config::read(self::CONFIG_APP);
97
		//_debug_array($config);
98
		$content = array_merge(self::gather_data(),array(
99
			'statistic_url' => self::STATISTIC_URL,
100
			'submit_host' => parse_url(self::SUBMIT_URL,PHP_URL_HOST),
101
			'submit_url'  => self::SUBMIT_URL,
102
			'last_submitted' => $config[self::CONFIG_LAST_SUBMIT],
103
		));
104
		//_debug_array($content);
105
106
		// show previous submit ID
107
		if ($config['statistics_submit_id'])
108
		{
109
			$content['submit_id'] = $config['statistics_submit_id'] == '***none***' ? '' : $config['statistics_submit_id'];
110
		}
111
		// show previous Api\Country
112
		if ($config[self::CONFIG_COUNTRY])
113
		{
114
			$content['country'] = $config[self::CONFIG_COUNTRY] == '***multinational***' ? '' : $config[self::CONFIG_COUNTRY];
115
		}
116
		// show previous usage_type
117
		if ($config[self::CONFIG_USAGE_TYPE])
118
		{
119
			$content['usage_type'] = $config[self::CONFIG_USAGE_TYPE];
120
		}
121
		// check if we detected svn or rpm/deb packages --> readonly
122
		if ($content['install_type'] && isset($sel_options['install_type'][$content['install_type']]))
123
		{
124
			$sel_options['install_type'] = array($content['install_type'] => $sel_options['install_type'][$content['install_type']]);
125
		}
126
		// else default to previous type
127
		elseif($config[self::CONFIG_INSTALL_TYPE])
128
		{
129
			$content['install_type'] = $config[self::CONFIG_INSTALL_TYPE];
130
		}
131
		// check if we are due for a new submission
132
		if (!isset($config[self::CONFIG_LAST_SUBMIT]) || $config[self::CONFIG_LAST_SUBMIT ] <= time()-self::SUBMISION_RATE)
133
		{
134
			// clear etemplate_exec_id and replace form.action, before submitting the form
135
			$content['onclick'] = "return app.admin.submit_statistic(this.form,'$content[submit_url]');";
136
		}
137
		else	// we are not due --> tell it the user
138
		{
139
			$readonlys['submit'] = $readonlys['postpone'] = true;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$readonlys was never initialized. Although not strictly required by PHP, it is generally a good practice to add $readonlys = array(); before regardless.
Loading history...
140
			$content['msg'] = lang('Your last submission was less then %1 days ago!',
141
				ceil((time()-$config[self::CONFIG_LAST_SUBMIT])/24/3600));
0 ignored issues
show
The call to lang() has too many arguments starting with ceil(time() - $config[se...ST_SUBMIT] / 24 / 3600). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

141
			$content['msg'] = /** @scrutinizer ignore-call */ lang('Your last submission was less then %1 days ago!',

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
142
		}
143
		$GLOBALS['egw_info']['flags']['app_header'] = lang('Submit statistic information');
144
		$tmpl = new Etemplate('admin.statistics');
145
		$tmpl->exec('admin.admin_statistics.submit',$content,$sel_options,$readonlys);
146
	}
147
148
	/**
149
	 * Gather statistical data to submit
150
	 *
151
	 * @return array key => value pairs
152
	 */
153
	protected static function gather_data()
154
	{
155
		// submit id is sha1 hash from install_id
156
		$data['submit_id'] = sha1($GLOBALS['egw_info']['server']['install_id']);
0 ignored issues
show
Comprehensibility Best Practice introduced by
$data was never initialized. Although not strictly required by PHP, it is generally a good practice to add $data = array(); before regardless.
Loading history...
157
158
		$data['country'] = $GLOBALS['egw_info']['user']['preferences']['common']['country'];
159
160
		// maintenance release (incl. EPL)
161
		$data['version'] = $GLOBALS['egw_info']['server']['versions']['maintenance_release'];
162
163
		// sessions in the last 30 days
164
		$data['sessions'] = $GLOBALS['egw']->db->query('SELECT COUNT(*) FROM egw_access_log WHERE li > '.(time()-30*24*3600))->fetchColumn();
165
166
		// total accounts from accounts table or ldap
167
		$GLOBALS['egw']->accounts->search(array(
168
			'type' => 'accounts',
169
			'start' => 0,
170
		));
171
		$data['users'] = $GLOBALS['egw']->accounts->total;
172
173
		$data['php'] = PHP_VERSION.': '.PHP_SAPI;
174
		$data['os'] = PHP_OS;
175
		// @ required to get ride of warning, if files are outside of open_basedir
176
		$matches = null;
177
		if (@file_exists($file = '/etc/lsb-release') && preg_match('/^DISTRIB_DESCRIPTION="?([^"]+)"?/mi', file_get_contents($file), $matches))
178
		{
179
			$data['os'] .= ': '.$matches[1];
180
		}
181
		elseif (@file_exists($file = '/etc/SuSE-release') || @file_exists($file = '/etc/redhat-release') || @file_exists($file = '/etc/debian_version'))
182
		{
183
			$data['os'] .= ': '.str_replace(array("\n","\r"),'',implode(',',file($file)));
184
		}
185
		if(file_exists(EGW_INCLUDE_ROOT.'/.git'))
186
		{
187
			$data['install_type'] = 'git';
188
		}
189
		elseif (file_exists('/entrypoint.sh'))
190
		{
191
			$data['install_type'] = 'docker';
192
		}
193
		elseif(EGW_INCLUDE_ROOT == '/usr/share/egroupware' && PHP_OS == 'Linux' && is_link('/usr/share/egroupware/header.inc.php'))
194
		{
195
			$data['install_type'] = 'package';
196
		}
197
		foreach(array_keys($GLOBALS['egw_info']['apps']) as $app)
198
		{
199
			if (in_array($app,array(
200
				'admin','phpgwapi','api','sambaadmin','developer_tools',
201
				'home','preferences','etemplate','registration','manual',
202
			)))
203
			{
204
				continue;	// --> ignore to not submit too much
205
			}
206
			if (($users = self::gather_app_users($app)))	// ignore apps noone is allowed to run
207
			{
208
				$data['apps'][$app] = $app.':'.round(100.0*$users/$data['users']).'%';
209
				if (($entries = self::gather_app_entries($app)))
210
				{
211
					$data['apps'][$app] .= ':'.$entries;
212
				}
213
			}
214
		}
215
		ksort($data['apps']);
216
		$data['apps'] = implode("\n",$data['apps']);
217
218
		return $data;
219
	}
220
221
	/**
222
	 * Get percentage of users allowed to use an application
223
	 *
224
	 * @param string $app
225
	 * @return int number of users allowed to run application
226
	 */
227
	static function gather_app_users($app)
228
	{
229
		$users = array();
230
		if (($access = $GLOBALS['egw']->acl->get_ids_for_location('run',1,$app)))
231
		{
232
			foreach($access as $uid)
233
			{
234
				if ($uid > 0)
235
				{
236
					$users[] = $uid;
237
				}
238
				elseif (($members = $GLOBALS['egw']->accounts->members($uid,true)))
239
				{
240
					$users = array_merge($users,$members);
241
				}
242
			}
243
			$users = array_unique($users);
244
		}
245
		return count($users);
246
	}
247
248
	/**
249
	 * Get percentage of users allowed to use an application
250
	 *
251
	 * @param string $app
252
	 * @return int
253
	 */
254
	static function gather_app_entries($app)
255
	{
256
		// main table for each application
257
		static $app2table = array(
258
			'addressbook' => 'egw_addressbook',
259
			'bookmarks'   => 'egw_bookmarks',
260
			'calendar'    => 'egw_cal_dates',
261
			'infolog'     => 'egw_infolog',
262
			'filemanager' => 'egw_sqlfs',
263
			'gallery'     => 'g2_Item',
264
			'news_admin'  => 'egw_news WHERE news_submittedby > 0',	// exclude imported rss feeds
265
			'polls'       => 'egw_polls',
266
			'projectmanager' => 'egw_pm_projects',
267
			'phpbrain'    => 'egw_kb_articles',
268
			'resources'   => 'egw_resources',
269
			'sitemgr'     => 'egw_sitemgr_pages',
270
			'syncml'      => 'egw_syncmlsummary',
271
			'timesheet'   => 'egw_timesheet',
272
			'tracker'     => 'egw_tracker',
273
			'wiki'        => 'egw_wiki_pages',
274
			'mydms'       => 'phpgw_mydms_Documents',
275
		);
276
		if (($table = $app2table[$app]))
277
		{
278
			try {
279
				$entries = (int)$GLOBALS['egw']->db->query('SELECT COUNT(*) FROM '.$table)->fetchColumn();
280
				//echo "$app ($table): $entries<br />\n";
281
			}
282
			catch(Api\Db\Exception $e) {
283
				unset($e);
284
				$entries = null;
285
			}
286
		}
287
		return $entries;
288
	}
289
290
	/**
291
	 * Check if next submission is due, in which case we call submit and NOT return to the admin hook
292
	 *
293
	 * @param boolean $redirect should we redirect or return true
294
	 * @return boolean true if statistic submission is due
295
	 */
296
	public static function check($redirect=true)
297
	{
298
		$config = Api\Config::read(self::CONFIG_APP);
299
300
		if (isset($config[self::CONFIG_POSTPONE_SUBMIT]) && $config[self::CONFIG_POSTPONE_SUBMIT] > time() ||
0 ignored issues
show
Consider adding parentheses for clarity. Current Interpretation: (IssetNode && $config[se... - self::SUBMISION_RATE, Probably Intended Meaning: IssetNode && ($config[se...- self::SUBMISION_RATE)
Loading history...
301
			isset($config[self::CONFIG_LAST_SUBMIT ]) && $config[self::CONFIG_LAST_SUBMIT ] > time()-self::SUBMISION_RATE)
302
		{
303
			return false;
304
		}
305
		if (!$redirect) return true;
306
307
		//die('Due for new statistics submission: last_submit='.$config[self::CONFIG_LAST_SUBMIT ].', postpone='.$config[self::CONFIG_POSTPONE_SUBMIT].', '.function_backtrace());
308
		Egw::redirect_link('/index.php',array(
309
			'menuaction' => 'admin.admin_ui.index',
310
			'ajax' => 'true',
311
			'load' => 'admin.admin_statistics.submit',
312
		));
313
	}
314
}
315