Fixed: Not selecting a datalabel used to issue a notice(undefined offset)
[phpmyadmin/ammaryasirr.git] / libraries / Advisor.class.php
blobb681155ac64c9471fcc73e5b9b2360b8ea6608fb
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. Adjusted to phpMyAdmin
7 * @package phpMyAdmin
8 */
10 class Advisor
12 var $variables;
13 var $parseResult;
14 var $runResult;
16 function run() {
17 // HowTo: A simple Advisory system in 3 easy steps.
19 // Step 1: Get some variables to evaluate on
20 $this->variables = array_merge(
21 PMA_DBI_fetch_result('SHOW GLOBAL STATUS', 0, 1),
22 PMA_DBI_fetch_result('SHOW GLOBAL VARIABLES', 0, 1)
24 // Add total memory to variables as well
25 require_once('libraries/sysinfo.lib.php');
26 $sysinfo = getSysInfo();
27 $memory = $sysinfo->memory();
28 $this->variables['system_memory'] = $memory['MemTotal'];
30 // Step 2: Read and parse the list of rules
31 $this->parseResult = $this->parseRulesFile();
32 // Step 3: Feed the variables to the rules and let them fire. Sets $runResult
33 $this->runRules();
35 return array('parse' => array('errors' => $this->parseResult['errors']), 'run' => $this->runResult);
38 function runRules() {
39 $this->runResult = array(
40 'fired' => array(),
41 'notfired' => array(),
42 'unchecked'=> array(),
43 'errors' => array()
46 foreach ($this->parseResult['rules'] as $rule) {
47 $this->variables['value'] = 0;
48 $precond = true;
50 if (isset($rule['precondition'])) {
51 try {
52 $precond = $this->ruleExprEvaluate($rule['precondition']);
53 } catch (Exception $e) {
54 $this->runResult['errors'][] = 'Failed evaluating precondition for rule \''.$rule['name'].'\'. PHP threw following error: '.$e->getMessage();
55 continue;
59 if (! $precond) {
60 $this->addRule('unchecked', $rule);
61 } else {
62 try {
63 $value = $this->ruleExprEvaluate($rule['formula']);
64 } catch(Exception $e) {
65 $this->runResult['errors'][] = 'Failed calculating value for rule \''.$rule['name'].'\'. PHP threw following error: '.$e->getMessage();
66 continue;
69 $this->variables['value'] = $value;
71 try {
72 if ($this->ruleExprEvaluate($rule['test'])) {
73 $this->addRule('fired', $rule);
74 } else {
75 $this->addRule('notfired', $rule);
77 } catch(Exception $e) {
78 $this->runResult['errors'][] = 'Failed running test for rule \''.$rule['name'].'\'. PHP threw following error: '.$e->getMessage();
83 return true;
86 /**
87 * Escapes percent string to be used in format string.
89 * @param string $str
90 * @return string
92 function escapePercent($str)
94 return preg_replace('/%( |,|\.|$)/','%%\1', $str);
97 /**
98 * Wrapper function for translating.
100 * @param string $str
101 * @param mixed $param
102 * @return string
104 function translate($str, $param = null)
106 if (is_null($param)) {
107 return sprintf(_gettext(Advisor::escapePercent($str)));
108 } else {
109 $printf = 'sprintf("' . _gettext(Advisor::escapePercent($str)) . '",';
110 return $this->ruleExprEvaluate(
111 $printf . $param . ')',
112 strlen($printf)
118 * Splits justification to text and formula.
120 * @param string $rule
121 * @return array
123 function splitJustification($rule)
125 $jst = preg_split('/\s*\|\s*/', $rule['justification'], 2);
126 if (count($jst) > 1) {
127 return array($jst[0], $jst[1]);
129 return array($rule['justification']);
132 // Adds a rule to the result list
133 function addRule($type, $rule)
135 switch($type) {
136 case 'notfired':
137 case 'fired':
138 $jst = Advisor::splitJustification($rule);
139 if (count($jst) > 1) {
140 try {
141 /* Translate */
142 $str = $this->translate($jst[0], $jst[1]);
143 } catch (Exception $e) {
144 $this->runResult['errors'][] = sprintf(
145 __('Failed formatting string for rule \'%s\'. PHP threw following error: %s'),
146 $rule['name'],
147 $e->getMessage()
149 return;
152 $rule['justification'] = $str;
153 } else {
154 $rule['justification'] = $this->translate($rule['justification']);
156 $rule['name'] = $this->translate($rule['name']);
157 $rule['issue'] = $this->translate($rule['issue']);
159 // Replaces {server_variable} with 'server_variable' linking to server_variables.php
160 $rule['recommendation'] = preg_replace(
161 '/\{([a-z_0-9]+)\}/Ui',
162 '<a href="server_variables.php?' . PMA_generate_common_url() . '#filter=\1">\1</a>',
163 $this->translate($rule['recommendation']));
165 // Replaces external Links with PMA_linkURL() generated links
166 $rule['recommendation'] = preg_replace(
167 '#href=("|\')(https?://[^\1]+)\1#ie',
168 '\'href="\' . PMA_linkURL("\2") . \'"\'',
169 $rule['recommendation']
171 break;
174 $this->runResult[$type][] = $rule;
177 private function ruleExprEvaluate_var1($matches)
179 // '/fired\s*\(\s*(\'|")(.*)\1\s*\)/Uie'
180 return '1'; //isset($this->runResult[\'fired\']
183 private function ruleExprEvaluate_var2($matches)
185 // '/\b(\w+)\b/e'
186 return isset($this->variables[$matches[1]])
187 ? (is_numeric($this->variables[$matches[1]])
188 ? $this->variables[$matches[1]]
189 : '"'.$this->variables[$matches[1]].'"')
190 : $matches[1];
193 // Runs a code expression, replacing variable names with their respective values
194 // ignoreUntil: if > 0, it doesn't replace any variables until that string position, but still evaluates the whole expr
195 function ruleExprEvaluate($expr, $ignoreUntil = 0)
197 if ($ignoreUntil > 0) {
198 $exprIgnore = substr($expr,0,$ignoreUntil);
199 $expr = substr($expr,$ignoreUntil);
201 $expr = preg_replace_callback('/fired\s*\(\s*(\'|")(.*)\1\s*\)/Ui', array($this, 'ruleExprEvaluate_var1'), $expr);
202 $expr = preg_replace_callback('/\b(\w+)\b/', array($this, 'ruleExprEvaluate_var2'), $expr);
203 if ($ignoreUntil > 0) {
204 $expr = $exprIgnore . $expr;
206 $value = 0;
207 $err = 0;
209 ob_start();
210 eval('$value = '.$expr.';');
211 $err = ob_get_contents();
212 ob_end_clean();
213 if ($err) {
214 throw new Exception(strip_tags($err) . '<br />Executed code: $value = '.$expr.';');
216 return $value;
219 // Reads the rule file into an array, throwing errors messages on syntax errors
220 function parseRulesFile()
222 $file = file('libraries/advisory_rules.txt');
223 $errors = array();
224 $rules = array();
225 $ruleSyntax = array('name','formula','test','issue','recommendation','justification');
226 $numRules = count($ruleSyntax);
227 $numLines = count($file);
228 $j = -1;
229 $ruleLine = -1;
231 for ($i = 0; $i<$numLines; $i++) {
232 $line = $file[$i];
233 if ($line[0] == '#' || $line[0] == "\n") {
234 continue;
237 // Reading new rule
238 if (substr($line, 0, 4) == 'rule') {
239 if ($ruleLine > 0) {
240 $errors[] = 'Invalid rule declaration on line '.($i+1). ', expected line '.$ruleSyntax[$ruleLine++].' of previous rule' ;
241 continue;
243 if (preg_match("/rule\s'(.*)'( \[(.*)\])?$/",$line,$match)) {
244 $ruleLine = 1;
245 $j++;
246 $rules[$j] = array( 'name' => $match[1]);
247 if(isset($match[3])) $rules[$j]['precondition'] = $match[3];
248 } else {
249 $errors[] = 'Invalid rule declaration on line '.($i+1);
251 continue;
252 } else {
253 if ($ruleLine == -1) {
254 $errors[] = 'Unexpected characters on line '.($i+1);
258 // Reading rule lines
259 if ($ruleLine > 0) {
260 if (!isset($line[0])) {
261 continue; // Empty lines are ok
263 // Non tabbed lines are not
264 if ($line[0] != "\t") {
265 $errors[] = 'Unexpected character on line '.($i+1).'
266 . Expected tab, but found \''.$line[0].'\'';
267 continue;
269 $rules[$j][$ruleSyntax[$ruleLine++]] = chop(substr($line,1));
272 // Rule complete
273 if ($ruleLine == $numRules) {
274 $ruleLine = -1;
278 return array('rules' => $rules, 'errors' => $errors);
282 function PMA_bytime($num, $precision)
284 $per = '';
285 if ($num >= 1) { # per second
286 $per = "per second";
287 } elseif ($num*60 >= 1) { # per minute
288 $num = $num*60;
289 $per = "per minute";
290 } elseif ($num*60*60 >=1 ) { # per hour
291 $num = $num*60*60;
292 $per = "per hour";
293 } else {
294 $num = $num*60*60*24;
295 $per = "per day";
298 $num = round($num, $precision);
300 if ($num == 0) {
301 $num = '<'.pow(10,-$precision);
304 return "$num $per";