Translated using Weblate (Kurdish Sorani)
[phpmyadmin.git] / libraries / Advisor.class.php
bloba8b5d0b075efbb2ad6d62bf8cd9605a4c621a25f
1 <?php
2 /* vim: set expandtab sw=4 ts=4 sts=4: */
3 /**
4 * A simple rules engine, that parses and executes the rules in advisory_rules.txt.
5 * Adjusted to phpMyAdmin.
7 * @package PhpMyAdmin
8 */
9 if (! defined('PHPMYADMIN')) {
10 exit;
13 /**
14 * Advisor class
16 * @package PhpMyAdmin
18 class Advisor
20 var $variables;
21 var $parseResult;
22 var $runResult;
24 /**
25 * Parses and executes advisor rules
27 * @return array with run and parse results
29 function run()
31 // HowTo: A simple Advisory system in 3 easy steps.
33 // Step 1: Get some variables to evaluate on
34 $this->variables = array_merge(
35 $GLOBALS['dbi']->fetchResult('SHOW GLOBAL STATUS', 0, 1),
36 $GLOBALS['dbi']->fetchResult('SHOW GLOBAL VARIABLES', 0, 1)
38 if (PMA_DRIZZLE) {
39 $this->variables = array_merge(
40 $this->variables,
41 $GLOBALS['dbi']->fetchResult(
42 "SELECT concat('Com_', variable_name), variable_value "
43 . "FROM data_dictionary.GLOBAL_STATEMENTS", 0, 1
47 // Add total memory to variables as well
48 include_once 'libraries/sysinfo.lib.php';
49 $sysinfo = PMA_getSysInfo();
50 $memory = $sysinfo->memory();
51 $this->variables['system_memory'] = $memory['MemTotal'];
53 // Step 2: Read and parse the list of rules
54 $this->parseResult = $this->parseRulesFile();
55 // Step 3: Feed the variables to the rules and let them fire. Sets
56 // $runResult
57 $this->runRules();
59 return array(
60 'parse' => array('errors' => $this->parseResult['errors']),
61 'run' => $this->runResult
65 /**
66 * Stores current error in run results.
68 * @param string $description description of an error.
69 * @param object $exception exception raised
71 * @return void
73 function storeError($description, $exception)
75 $this->runResult['errors'][] = $description
76 . ' '
77 . sprintf(
78 __('PHP threw following error: %s'),
79 $exception->getMessage()
83 /**
84 * Executes advisor rules
86 * @return void
88 function runRules()
90 $this->runResult = array(
91 'fired' => array(),
92 'notfired' => array(),
93 'unchecked'=> array(),
94 'errors' => array()
97 foreach ($this->parseResult['rules'] as $rule) {
98 $this->variables['value'] = 0;
99 $precond = true;
101 if (isset($rule['precondition'])) {
102 try {
103 $precond = $this->ruleExprEvaluate($rule['precondition']);
104 } catch (Exception $e) {
105 $this->storeError(
106 sprintf(
107 __('Failed evaluating precondition for rule \'%s\'.'),
108 $rule['name']
112 continue;
116 if (! $precond) {
117 $this->addRule('unchecked', $rule);
118 } else {
119 try {
120 $value = $this->ruleExprEvaluate($rule['formula']);
121 } catch(Exception $e) {
122 $this->storeError(
123 sprintf(
124 __('Failed calculating value for rule \'%s\'.'),
125 $rule['name']
129 continue;
132 $this->variables['value'] = $value;
134 try {
135 if ($this->ruleExprEvaluate($rule['test'])) {
136 $this->addRule('fired', $rule);
137 } else {
138 $this->addRule('notfired', $rule);
140 } catch(Exception $e) {
141 $this->storeError(
142 sprintf(
143 __('Failed running test for rule \'%s\'.'),
144 $rule['name']
152 return true;
156 * Escapes percent string to be used in format string.
158 * @param string $str string to escape
160 * @return string
162 static function escapePercent($str)
164 return preg_replace('/%( |,|\.|$|\(|\)|<|>)/', '%%\1', $str);
168 * Wrapper function for translating.
170 * @param string $str the string
171 * @param mixed $param the parameters
173 * @return string
175 function translate($str, $param = null)
177 if (is_null($param)) {
178 return sprintf(_gettext(Advisor::escapePercent($str)));
179 } else {
180 $printf = 'sprintf("' . _gettext(Advisor::escapePercent($str)) . '",';
181 return $this->ruleExprEvaluate(
182 $printf . $param . ')',
183 strlen($printf)
189 * Splits justification to text and formula.
191 * @param string $rule the rule
193 * @return array
195 static function splitJustification($rule)
197 $jst = preg_split('/\s*\|\s*/', $rule['justification'], 2);
198 if (count($jst) > 1) {
199 return array($jst[0], $jst[1]);
201 return array($rule['justification']);
205 * Adds a rule to the result list
207 * @param string $type type of rule
208 * @param array $rule rule itslef
210 * @return void
212 function addRule($type, $rule)
214 switch($type) {
215 case 'notfired':
216 case 'fired':
217 $jst = Advisor::splitJustification($rule);
218 if (count($jst) > 1) {
219 try {
220 /* Translate */
221 $str = $this->translate($jst[0], $jst[1]);
222 } catch (Exception $e) {
223 $this->storeError(
224 sprintf(
225 __('Failed formatting string for rule \'%s\'.'),
226 $rule['name']
230 return;
233 $rule['justification'] = $str;
234 } else {
235 $rule['justification'] = $this->translate($rule['justification']);
237 $rule['id'] = $rule['name'];
238 $rule['name'] = $this->translate($rule['name']);
239 $rule['issue'] = $this->translate($rule['issue']);
241 // Replaces {server_variable} with 'server_variable'
242 // linking to server_variables.php
243 $rule['recommendation'] = preg_replace(
244 '/\{([a-z_0-9]+)\}/Ui',
245 '<a href="server_variables.php?' . PMA_URL_getCommon()
246 . '&filter=\1">\1</a>',
247 $this->translate($rule['recommendation'])
250 // Replaces external Links with PMA_linkURL() generated links
251 $rule['recommendation'] = preg_replace_callback(
252 '#href=("|\')(https?://[^\1]+)\1#i',
253 array($this, '_replaceLinkURL'),
254 $rule['recommendation']
256 break;
259 $this->runResult[$type][] = $rule;
263 * Callback for wrapping links with PMA_linkURL
265 * @param array $matches List of matched elements form preg_replace_callback
267 * @return Replacement value
269 private function _replaceLinkURL($matches)
271 return 'href="' . PMA_linkURL($matches[2]) . '"';
275 * Callback for evaluating fired() condition.
277 * @param array $matches List of matched elements form preg_replace_callback
279 * @return Replacement value
281 private function _ruleExprEvaluateFired($matches)
283 // No list of fired rules
284 if (!isset($this->runResult['fired'])) {
285 return '0';
288 // Did matching rule fire?
289 foreach ($this->runResult['fired'] as $rule) {
290 if ($rule['id'] == $matches[2]) {
291 return '1';
295 return '0';
299 * Callback for evaluating variables in expression.
301 * @param array $matches List of matched elements form preg_replace_callback
303 * @return Replacement value
305 private function _ruleExprEvaluateVariable($matches)
307 if (! isset($this->variables[$matches[1]])) {
308 return $matches[1];
310 if (is_numeric($this->variables[$matches[1]])) {
311 return $this->variables[$matches[1]];
312 } else {
313 return '\'' . addslashes($this->variables[$matches[1]]) . '\'';
318 * Runs a code expression, replacing variable names with their respective
319 * values
321 * @param string $expr expression to evaluate
322 * @param int $ignoreUntil if > 0, it doesn't replace any variables until
323 * that string position, but still evaluates the
324 * whole expr
326 * @return result of evaluated expression
328 function ruleExprEvaluate($expr, $ignoreUntil = 0)
330 if ($ignoreUntil > 0) {
331 $exprIgnore = substr($expr, 0, $ignoreUntil);
332 $expr = substr($expr, $ignoreUntil);
334 // Evaluate fired() conditions
335 $expr = preg_replace_callback(
336 '/fired\s*\(\s*(\'|")(.*)\1\s*\)/Ui',
337 array($this, '_ruleExprEvaluateFired'),
338 $expr
340 // Evaluate variables
341 $expr = preg_replace_callback(
342 '/\b(\w+)\b/',
343 array($this, '_ruleExprEvaluateVariable'),
344 $expr
346 if ($ignoreUntil > 0) {
347 $expr = $exprIgnore . $expr;
349 $value = 0;
350 $err = 0;
352 // Actually evaluate the code
353 ob_start();
354 eval('$value = ' . $expr . ';');
355 $err = ob_get_contents();
356 ob_end_clean();
358 // Error handling
359 if ($err) {
360 throw new Exception(
361 strip_tags($err)
362 . '<br />Executed code: $value = ' . htmlspecialchars($expr) . ';'
365 return $value;
369 * Reads the rule file into an array, throwing errors messages on syntax
370 * errors.
372 * @return array with parsed data
374 static function parseRulesFile()
376 $file = file('libraries/advisory_rules.txt', FILE_IGNORE_NEW_LINES);
377 $errors = array();
378 $rules = array();
379 $lines = array();
380 $ruleSyntax = array(
381 'name', 'formula', 'test', 'issue', 'recommendation', 'justification'
383 $numRules = count($ruleSyntax);
384 $numLines = count($file);
385 $ruleNo = -1;
386 $ruleLine = -1;
388 for ($i = 0; $i < $numLines; $i++) {
389 $line = $file[$i];
390 if ($line == "" || $line[0] == '#') {
391 continue;
394 // Reading new rule
395 if (substr($line, 0, 4) == 'rule') {
396 if ($ruleLine > 0) {
397 $errors[] = sprintf(
398 __('Invalid rule declaration on line %1$s, expected line %2$s of previous rule.'),
399 $i + 1,
400 $ruleSyntax[$ruleLine++]
402 continue;
404 if (preg_match("/rule\s'(.*)'( \[(.*)\])?$/", $line, $match)) {
405 $ruleLine = 1;
406 $ruleNo++;
407 $rules[$ruleNo] = array('name' => $match[1]);
408 $lines[$ruleNo] = array('name' => $i + 1);
409 if (isset($match[3])) {
410 $rules[$ruleNo]['precondition'] = $match[3];
411 $lines[$ruleNo]['precondition'] = $i + 1;
413 } else {
414 $errors[] = sprintf(
415 __('Invalid rule declaration on line %s.'),
416 $i + 1
419 continue;
420 } else {
421 if ($ruleLine == -1) {
422 $errors[] = sprintf(
423 __('Unexpected characters on line %s.'),
424 $i + 1
429 // Reading rule lines
430 if ($ruleLine > 0) {
431 if (!isset($line[0])) {
432 continue; // Empty lines are ok
434 // Non tabbed lines are not
435 if ($line[0] != "\t") {
436 $errors[] = sprintf(
437 __('Unexpected character on line %1$s. Expected tab, but found "%2$s".'),
438 $i + 1,
439 $line[0]
441 continue;
443 $rules[$ruleNo][$ruleSyntax[$ruleLine]] = chop(substr($line, 1));
444 $lines[$ruleNo][$ruleSyntax[$ruleLine]] = $i + 1;
445 $ruleLine += 1;
448 // Rule complete
449 if ($ruleLine == $numRules) {
450 $ruleLine = -1;
454 return array('rules' => $rules, 'lines' => $lines, 'errors' => $errors);
459 * Formats interval like 10 per hour
461 * @param integer $num number to format
462 * @param intefer $precision required precision
464 * @return formatted string
466 function ADVISOR_bytime($num, $precision)
468 $per = '';
469 if ($num >= 1) { // per second
470 $per = __('per second');
471 } elseif ($num * 60 >= 1) { // per minute
472 $num = $num * 60;
473 $per = __('per minute');
474 } elseif ($num * 60 * 60 >= 1 ) { // per hour
475 $num = $num * 60 * 60;
476 $per = __('per hour');
477 } else {
478 $num = $num * 60 * 60 * 24;
479 $per = __('per day');
482 $num = round($num, $precision);
484 if ($num == 0) {
485 $num = '<' . PMA_Util::pow(10, -$precision);
488 return "$num $per";
492 * Wrapper for PMA_Util::timespanFormat
494 * @param int $seconds the timespan
496 * @return string the formatted value
498 function ADVISOR_timespanFormat($seconds)
500 return PMA_Util::timespanFormat($seconds);
504 * Wrapper around PMA_Util::formatByteDown
506 * @param double $value the value to format
507 * @param int $limes the sensitiveness
508 * @param int $comma the number of decimals to retain
510 * @return array the formatted value and its unit
512 function ADVISOR_formatByteDown($value, $limes = 6, $comma = 0)
514 return PMA_Util::formatByteDown($value, $limes, $comma);