PageRenderTime 66ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 1ms

/inc/web.php

https://bitbucket.org/yoander/mtrack
PHP | 1402 lines | 1193 code | 144 blank | 65 comment | 253 complexity | fdd448a20ac97ff8ea360a561bb6f09b MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0
  1. <?php # vim:ts=2:sw=2:et:
  2. /* For licensing and copyright terms, see the file named LICENSE */
  3. /* Simplistic pathinfo parsing - could optionally have additional features such
  4. as validation added */
  5. function mtrack_parse_pathinfo($vars) {
  6. $pi = isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : '';
  7. $data = explode('/', $pi);
  8. $i = 0;
  9. $return_vars = array();
  10. array_shift($data);
  11. foreach($vars as $name => $value) {
  12. if (isset($data[$i])) {
  13. $return_vars[$name] = $data[$i];
  14. $i++;
  15. } else {
  16. $return_vars[$name] = $value;
  17. }
  18. }
  19. return $return_vars;
  20. }
  21. /**
  22. * Pathinfo retrieval minus starting slash
  23. * @param bool $no_strip Set to TRUE if you want to keep the prepended slash
  24. * @return null|string
  25. */
  26. function mtrack_get_pathinfo($no_strip = false) {
  27. $pi = isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : "";
  28. if (!empty($pi) && $no_strip == false) {
  29. $pi = substr($pi, 1);
  30. }
  31. // If the value is empty (by default or by stripping off the slash) replace it with null.
  32. if (empty($pi)) {
  33. $pi = null;
  34. }
  35. return $pi;
  36. }
  37. function mtrack_calc_root()
  38. {
  39. /* ABSWEB: the absolute URL to the base of the web app */
  40. global $ABSWEB;
  41. /* if they have one, use the weburl config value for this */
  42. $ABSWEB = MTrackConfig::get('core', 'weburl');
  43. if (strlen($ABSWEB)) {
  44. return;
  45. }
  46. /* otherwise, determine the root of the app.
  47. * This is complicated because the DOCUMENT_ROOT may refer to an area that
  48. * is completely unrelated to the actual root of the web application, for
  49. * instance, in the case that the user has a public_html dir where they
  50. * are running mtrack */
  51. /* determine the root of the app */
  52. $sdir = dirname($_SERVER['SCRIPT_FILENAME']);
  53. $idir = dirname(dirname(__FILE__)) . '/web';
  54. $diff = substr($sdir, strlen($idir)+1);
  55. $rel = preg_replace('@[^/]+@', '..', $diff);
  56. if (strlen($rel)) {
  57. $rel .= '/';
  58. }
  59. /* $rel is now the relative path to the root of the web app, from the current
  60. * page */
  61. if (isset($_SERVER['HTTP_HOST'])) {
  62. $ABSWEB = ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ?
  63. 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'];
  64. } else {
  65. $ABSWEB = 'http://localhost';
  66. }
  67. $bits = explode('/', $rel);
  68. $base = $_SERVER['SCRIPT_NAME'];
  69. foreach ($bits as $b) {
  70. $base = dirname($base);
  71. }
  72. if ($base == '/') {
  73. $ABSWEB .= '/';
  74. } else {
  75. $ABSWEB .= $base . '/';
  76. }
  77. }
  78. mtrack_calc_root();
  79. function mtrack_head($title, $navbar = true)
  80. {
  81. global $ABSWEB;
  82. static $mtrack_did_head;
  83. $whoami = mtrack_username(MTrackAuth::whoami(),
  84. array(
  85. 'no_image' => true
  86. )
  87. );
  88. if ($mtrack_did_head) {
  89. return;
  90. }
  91. $mtrack_did_head = true;
  92. $projectname = htmlentities(MTrackConfig::get('core', 'projectname'),
  93. ENT_QUOTES, 'utf-8');
  94. $logo = MTrackConfig::get('core', 'projectlogo');
  95. if (strlen($logo)) {
  96. $projectname = "<img alt='$projectname' src='$logo'>";
  97. }
  98. $fav = MTrackConfig::get('core', 'favicon');
  99. if (strlen($fav)) {
  100. $fav = <<<HTML
  101. <link rel="icon" href="$fav" type="image/x-icon" />
  102. <link rel="shortcut icon" href="$fav" type="image/x-icon" />
  103. HTML;
  104. } else {
  105. $fav = '';
  106. }
  107. $title = htmlentities($title, ENT_QUOTES, 'utf-8');
  108. $userinfo = $whoami;
  109. MTrackNavigation::augmentUserInfo($userinfo);
  110. echo <<<HTML
  111. <!DOCTYPE html>
  112. <html>
  113. <head>
  114. <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  115. <meta http-equiv="X-UA-Compatible" content="IE=8">
  116. <title>$title</title>
  117. $fav
  118. <link rel="stylesheet" href="${ABSWEB}css.php?2" type="text/css" />
  119. <script language="javascript" type="text/javascript" src="${ABSWEB}js.php?3"></script>
  120. </head>
  121. <body>
  122. HTML;
  123. if ($navbar) {
  124. echo <<<HTML
  125. <div class='navbar navbar-fixed-top' id='mainnav'>
  126. <div class='navbar-inner'>
  127. <div class='container'>
  128. <a class="btn btn-navbar"
  129. data-toggle="collapse" data-target=".nav-collapse">
  130. <span class='icon-bar'></span>
  131. <span class='icon-bar'></span>
  132. <span class='icon-bar'></span>
  133. </a>
  134. <a class='brand' href='#'>$projectname</a>
  135. <div class='nav-collapse'>
  136. HTML;
  137. $nav = array();
  138. if (MTrackAuth::whoami() !== 'anonymous') {
  139. $nav['/'] = 'Today';
  140. }
  141. $navcandidates = array(
  142. "/browse.php" => array("Browse", 'read', 'Browser'),
  143. "/wiki.php" => array("Wiki", 'read', 'Wiki'),
  144. "/timeline.php" => array("Timeline", 'read', 'Timeline'),
  145. "/roadmap.php" => array("Roadmap", 'read', 'Roadmap'),
  146. "/reports.php" => array("Reports", 'read', 'Reports'),
  147. "/ticket.php/new" => array("New Ticket", 'create', 'Tickets'),
  148. "/snippet.php" => array("Snippets", 'read', 'Snippets'),
  149. "/admin/" => array("Administration", 'modify', 'Enumerations', 'Components', 'Projects', 'Browser'),
  150. "/help.php" => array("Help", 'read', 'Wiki'),
  151. );
  152. foreach ($navcandidates as $url => $data) {
  153. $label = array_shift($data);
  154. $right = array_shift($data);
  155. $ok = false;
  156. foreach ($data as $object) {
  157. if (MTrackACL::hasAllRights($object, $right)) {
  158. $ok = true;
  159. break;
  160. }
  161. }
  162. if ($ok) {
  163. $nav[$url] = $label;
  164. }
  165. }
  166. echo mtrack_nav('mainnav', $nav, $userinfo);
  167. echo <<<HTML
  168. <form class='navbar-search pull-right'
  169. id="mainsearch" action="${ABSWEB}search.php">
  170. <input type="text" class="search-query" title="Search"
  171. name="q" accesskey="f">
  172. </form>
  173. </div>
  174. <div id="ajaxspin"></div>
  175. </div>
  176. </div>
  177. </div>
  178. HTML;
  179. }
  180. $not_configured = false;
  181. $party_addrs = MTrackConfig::get('core', 'admin_party_remote_address');
  182. if (MTrackConfig::get('core', 'admin_party') == 1 &&
  183. MTrackAuth::whoami() == 'adminparty' &&
  184. in_array($_SERVER['REMOTE_ADDR'], explode(',', $party_addrs))) {
  185. if ($_SERVER['SCRIPT_FILENAME'] !=
  186. dirname(dirname(__FILE__)) . '/web/admin/auth.php') {
  187. $perms = '';
  188. $path = MTrackConfig::getRuntimeConfigPath();
  189. if (!is_writable($path)) {
  190. $perms = "<br><p><b>$path</b> is NOT writable; please ensure that the permissions on your vardir are correct!</p>";
  191. }
  192. echo <<<HTML
  193. <div class='alert alert-error'>
  194. <a class='close' data-dismiss='alert'>&times;</a>
  195. <h4 class='alert-heading'>Welcome to the admin party!</h4>
  196. <p>
  197. Authentication is not yet configured;
  198. while it is in this state, any user connecting from <b>$party_addrs</b>
  199. is treated as having admin rights (that includes you, and this
  200. is why you are seeing this message). All other users are denied
  201. access.</p>
  202. $perms
  203. <br>
  204. <p><a class='btn btn-danger' href="{$ABSWEB}admin/auth.php">Click here to Configure Authentication</a></p>
  205. </div>
  206. HTML;
  207. }
  208. } elseif (MTrackConfig::get('core', 'admin_party') == 1)
  209. {
  210. $localaddr = preg_replace('@^(https?://)([^/]+)/(.*)$@',
  211. "\${1}127.0.0.1/\\3", $ABSWEB);
  212. $remoteaddr = htmlentities($_SERVER['REMOTE_ADDR']);
  213. echo <<<HTML
  214. <div class='alert alert-error'>
  215. <a class='close' data-dismiss='alert'>&times;</a>
  216. <h4 class='alert-heading'>Authentication is not yet configured</h4>
  217. If you are the admin,
  218. you should use the <b><a href="$localaddr">localhost address</a></b>
  219. to reach the system and configure it, or configure the <b>admin_party_remote_address</b> option to include <b>$remoteaddr</b>.
  220. </div>
  221. HTML;
  222. $not_configured = true;
  223. } elseif (!MTrackAuth::isAuthConfigured()) {
  224. echo <<<HTML
  225. <div class='alert alert-error'>
  226. <a class='close' data-dismiss='alert'>&times;</a>
  227. <b>Authentication is not yet configured</b>. If you are the admin,
  228. you will need to edit the config.ini or var/runtime.config file to
  229. configure authentication.
  230. </div>
  231. HTML;
  232. $not_configured = true;
  233. }
  234. if (preg_match("/(on|true|1)/i", ini_get('magic_quotes_gpc'))) {
  235. echo <<<HTML
  236. <div class='alert alert-error'>
  237. <b>magic_quotes_gpc</b> is enabled. This causes mtrack not to work.
  238. Disable this setting in your server configuration.
  239. </div>
  240. HTML;
  241. $not_configured = true;
  242. }
  243. echo <<<HTML
  244. </div>
  245. <div id="content">
  246. HTML;
  247. if ($not_configured) {
  248. mtrack_foot();
  249. exit;
  250. }
  251. }
  252. function mtrack_foot($visible_markup = true, $show_footer = false)
  253. {
  254. /* close the content div */
  255. echo <<<HTML
  256. </div>
  257. HTML;
  258. if ($visible_markup) {
  259. if ($show_footer) {
  260. echo <<<HTML
  261. <footer id="footer">
  262. <div class="navfoot">
  263. Powered by <a href="http://bitbucket.org/wez/mtrack/">mtrack</a>
  264. </div>
  265. </footer>
  266. HTML;
  267. }
  268. echo <<<HTML
  269. </body>
  270. </html>
  271. HTML;
  272. if (MTrackConfig::get('core', 'debug.footer')) {
  273. global $FORKS;
  274. echo "<!-- " . MTrackDB::$queries . " queries\n";
  275. var_export(MTrackDB::$query_strings);
  276. echo "\n\nforks\n\n";
  277. var_export($FORKS);
  278. echo "-->";
  279. }
  280. }
  281. }
  282. interface IMTrackExtensionPage {
  283. /** called to dispatch a page render */
  284. function dispatchRequest();
  285. }
  286. class MTrackExtensionPage {
  287. static $locations = array();
  288. static function registerLocation($location, IMTrackExtensionPage $page) {
  289. self::$locations[$location] = $page;
  290. }
  291. static function locationToURL($location) {
  292. global $ABSWEB;
  293. return $ABSWEB . 'ext.php/' . $location;
  294. }
  295. static function bindToPage($location) {
  296. while (strlen($location)) {
  297. if (isset(self::$locations[$location])) {
  298. return self::$locations[$location];
  299. }
  300. if (strpos($location, '/') === false) {
  301. return null;
  302. }
  303. $location = dirname($location);
  304. }
  305. }
  306. }
  307. interface IMTrackNavigationHelper {
  308. /** called by mtrack_nav
  309. * You may remove items from or add items to the items array by
  310. * changing the $items array.
  311. * Should you want to suppress the Wiki from navigation, you may
  312. * do so like this:
  313. * if ($id == 'mainnav') {
  314. * unset($items['/wiki.php']);
  315. * }
  316. * If you want to add an item, the key is the URL and the value
  317. * is the label. The label is raw HTML.
  318. */
  319. function augmentNavigation($id, &$items);
  320. /** called by mtrack_head
  321. * You may augment or override the "Logged in as user" text by
  322. * changing the $content variable */
  323. function augmentUserInfo(&$content);
  324. }
  325. class MTrackNavigation {
  326. static $helpers = array();
  327. static function registerHelper(IMTrackNavigationHelper $helper)
  328. {
  329. self::$helpers[] = $helper;
  330. }
  331. static function augmentNavigation($id, &$items)
  332. {
  333. foreach (self::$helpers as $helper) {
  334. $helper->augmentNavigation($id, $items);
  335. }
  336. }
  337. static function augmentUserInfo(&$content)
  338. {
  339. foreach (self::$helpers as $helper) {
  340. $helper->augmentUserInfo($content);
  341. }
  342. }
  343. }
  344. function mtrack_breadcrumb($path, $base_url = null)
  345. {
  346. if ($path[0] != '/') {
  347. $path = '/' . $path;
  348. }
  349. if ($path == '/') {
  350. $crumbs = array('');
  351. } else {
  352. $crumbs = explode('/', $path);
  353. }
  354. $html = "<ul class='breadcrumb'>";
  355. $location = $base_url;
  356. foreach ($crumbs as $i => $ele) {
  357. if (!strlen($ele)) {
  358. $path = '<i class="icon-home"></i> ';
  359. } else {
  360. $location .= '/' . urlencode($ele);
  361. $path = htmlentities($ele, ENT_QUOTES, 'utf-8');
  362. }
  363. if ($i == count($crumbs) - 1) {
  364. $html .= "<li class='active'>";
  365. } else {
  366. $html .= "<li>";
  367. }
  368. $html .= "<a href='$location'>$path</a> ";
  369. if ($i < count($crumbs) - 1) {
  370. $html .= "<span class='divider'>/</span>";
  371. }
  372. $html .= "</li>";
  373. }
  374. $html .= "</ul>";
  375. return $html;
  376. }
  377. function mtrack_nav($id, $nav, $userinfo) {
  378. global $ABSWEB;
  379. // Allow config file to manipulate the navigation bits
  380. $cnav = MTrackConfig::getSection('nav:' . $id);
  381. if (is_array($cnav)) {
  382. foreach ($cnav as $loc => $label) {
  383. if (!strlen($label)) {
  384. unset($nav[$loc]);
  385. } else {
  386. $nav[$loc] = $label;
  387. }
  388. }
  389. }
  390. MTrackNavigation::augmentNavigation($id, $nav);
  391. $elements = array();
  392. $web = realpath(dirname(__FILE__) . '/../web');
  393. $where = substr($_SERVER['SCRIPT_FILENAME'], strlen($web));
  394. if (isset($_SERVER['PATH_INFO'])) {
  395. $where .= $_SERVER['PATH_INFO'];
  396. }
  397. $active = null;
  398. $tries = 0;
  399. do {
  400. foreach ($nav as $loc => $label) {
  401. $cloc = $loc;
  402. if (!strncmp($cloc, $ABSWEB, strlen($ABSWEB))) {
  403. $cloc = substr($cloc, strlen($ABSWEB)-1);
  404. }
  405. if ($where == $cloc || $where == rtrim($cloc, '/')) {
  406. $active = $loc;
  407. break;
  408. }
  409. }
  410. $where = dirname($where);
  411. } while ($active === null && $tries++ < 100);
  412. $drop = array();
  413. $active_in_drop = false;
  414. foreach ($nav as $loc => $label) {
  415. $class = '';
  416. if ($active == $loc) {
  417. $class = ' class="active"';
  418. }
  419. if ($loc[0] == '/') {
  420. $url = substr($loc, 1); // trim off leading /
  421. } else {
  422. $url = $loc;
  423. }
  424. if (!preg_match('/^[a-z-]+:/', $url)) {
  425. $url = $ABSWEB . $url;
  426. }
  427. if (count($elements) > 6) {
  428. if (strlen($class)) $active_in_drop = true;
  429. $drop[] = "<li><a href=\"$url\">$label</a></li>";
  430. } else {
  431. $elements[] = "<li$class><a href=\"$url\">$label</a></li>";
  432. }
  433. }
  434. if (count($drop)) {
  435. $class = $active_in_drop ? ' active' : '';
  436. $elements[] = "<li class='dropdown$class'>
  437. <a href='#' class='dropdown-toggle' data-toggle='dropdown'
  438. >More <b class='caret'></b></a><ul class='dropdown-menu'>" .
  439. join("\n", $drop) . "</ul></li>";
  440. }
  441. if ($userinfo) {
  442. $me = MTrackAuth::whoami();
  443. if (MTrackAuth::canLogOut() && $me != 'anonymous') {
  444. $logout = $GLOBALS['ABSWEB'] . 'logout.php';
  445. $elements[] = "<li class='dropdown'>
  446. <a href='#' class='dropdown-toggle' data-toggle='dropdown'
  447. >$me <b class='caret'></b></a><ul class='dropdown-menu'>
  448. <li>$userinfo</li>
  449. <li><a href='$logout'>Log Out</a></li>
  450. </ul></li>";
  451. } else {
  452. $elements[] = "<li>" . $userinfo . "</li>";
  453. }
  454. }
  455. return "<ul class='nav'>" . implode('', $elements) . "</ul>";
  456. }
  457. function mtrack_date($tstring, $show_full = false)
  458. {
  459. /* database time is always relative to UTC */
  460. $d = date_create($tstring, new DateTimeZone('UTC'));
  461. if (!is_object($d)) {
  462. throw new Exception("could not represent $tstring as a datetime object");
  463. }
  464. $iso8601 = $d->format(DateTime::W3C);
  465. /* but we want to render relative to user prefs */
  466. date_timezone_set($d, new DateTimeZone(date_default_timezone_get()));
  467. $full = $d->format('D, M d Y H:i');
  468. if (!$show_full) {
  469. return "<abbr title=\"$iso8601\" class='timeinterval'>$full</abbr>";
  470. }
  471. return "<abbr title='$iso8601' class='timeinterval'>$full</abbr> <span class='fulldate'>$full</span>";
  472. }
  473. function mtrack_mkdir_p($dir)
  474. {
  475. if (is_dir($dir)) return true;
  476. $parent = dirname($dir);
  477. if (!is_dir($parent)) {
  478. mtrack_mkdir_p($parent);
  479. }
  480. return mkdir($dir);
  481. }
  482. function mtrack_rmdir($dir)
  483. {
  484. $files = scandir($dir);
  485. if (!$files)
  486. return;
  487. foreach ($files as $ent) {
  488. if ($ent == '.' || $ent == '..') {
  489. continue;
  490. }
  491. $full = $dir . DIRECTORY_SEPARATOR . $ent;
  492. if (is_dir($full)) {
  493. mtrack_rmdir($full);
  494. } else {
  495. unlink($full);
  496. }
  497. }
  498. rmdir($dir);
  499. }
  500. function mtrack_make_temp_dir($do_make = true, $tempdir = null)
  501. {
  502. if ($tempdir === null) {
  503. $tempdir = sys_get_temp_dir();
  504. }
  505. $base = $tempdir . DIRECTORY_SEPARATOR . "mtrack." . uniqid();
  506. for ($i = 0; $i < 1024; $i++) {
  507. $candidate = $base . sprintf("%04x", $i);
  508. if ($do_make) {
  509. if (mkdir($candidate)) {
  510. return $candidate;
  511. }
  512. } else {
  513. /* racy */
  514. if (!file_exists($candidate) && !is_dir($candidate)) {
  515. return $candidate;
  516. }
  517. }
  518. }
  519. throw new Exception("unable to make temp dir based on path $candidate. Check permissions and ownership on vardir and ensure that it is writable to the webserver process");
  520. }
  521. function mtrack_diff_strings($before, $now)
  522. {
  523. $tempdir = sys_get_temp_dir();
  524. $afile = tempnam($tempdir, "mtrack");
  525. $bfile = tempnam($tempdir, "mtrack");
  526. file_put_contents($afile, $before);
  527. file_put_contents($bfile, $now);
  528. $diff = MTrackConfig::get('tools', 'diff');
  529. if (PHP_OS == 'SunOS') {
  530. // TODO: make an option to allow use of gnu diff on solaris
  531. $diff = shell_exec("$diff -u $afile $bfile");
  532. $diff = str_replace($afile, 'before', $diff);
  533. $diff = str_replace($bfile, 'now', $diff);
  534. } else {
  535. $diff = shell_exec("$diff --label before --label now -u $afile $bfile");
  536. }
  537. unlink($afile);
  538. unlink($bfile);
  539. $diff = htmlentities($diff, ENT_COMPAT, 'utf-8');
  540. return $diff;
  541. }
  542. function mtrack_last_chance_saloon($e)
  543. {
  544. if ($e instanceof MTrackAuthorizationException) {
  545. if (MTrackAuth::whoami() == 'anonymous') {
  546. MTrackAuth::forceAuthenticate();
  547. }
  548. mtrack_head('Insufficient Privilege');
  549. echo '<h1>Insufficient Privilege</h1>';
  550. $rights = is_array($e->rights) ? join(', ', $e->rights) : $e->rights;
  551. echo "You do not have the required set of rights ($rights) to access this page<br>";
  552. mtrack_foot();
  553. exit;
  554. }
  555. $msg = $e->getMessage();
  556. try {
  557. mtrack_head('Whoops: ' . $msg);
  558. } catch (Exception $doublefault) {
  559. }
  560. echo "<h1>An error occurred!</h1>";
  561. echo htmlentities($msg, ENT_QUOTES, 'utf-8');
  562. echo "<br>";
  563. echo nl2br(htmlentities($e->getTraceAsString(), ENT_QUOTES, 'utf-8'));
  564. try {
  565. mtrack_foot();
  566. } catch (Exception $doublefault) {
  567. }
  568. }
  569. function mtrack_canon_username($username)
  570. {
  571. static $canon_map = null;
  572. if ($canon_map === null) {
  573. $canon_map = array();
  574. foreach (MTrackDB::q('select alias, userid from useraliases union select email, userid from userinfo where email <> \'\'')->fetchAll()
  575. as $row) {
  576. $canon_map[$row[0]] = $row[1];
  577. }
  578. }
  579. $runaway = 25;
  580. do {
  581. if (isset($canon_map[$username])) {
  582. if ($username == $canon_map[$username]) {
  583. break;
  584. }
  585. $username = $canon_map[$username];
  586. } elseif (preg_match('/<([a-z0-9_.+=-]+@[a-z0-9.-]+)>/', $username, $M)) {
  587. // look at just the email address
  588. $username = $M[1];
  589. if (!isset($canon_map[$username])) {
  590. break;
  591. }
  592. } else {
  593. break;
  594. }
  595. } while ($runaway-- > 0);
  596. return $username;
  597. }
  598. function mtrack_username($username, $options = array())
  599. {
  600. $username = mtrack_canon_username($username);
  601. $userdata = MTrackAuth::getUserData($username);
  602. if (isset($userdata['fullname']) && strlen($userdata['fullname'])) {
  603. $title = " title='" .
  604. htmlentities($userdata['fullname'], ENT_QUOTES, 'utf-8') . "' ";
  605. } else {
  606. $title = '';
  607. }
  608. global $ABSWEB;
  609. if (!isset($options['size'])) {
  610. $options['size'] = 24;
  611. }
  612. if (isset($options['class'])) {
  613. $extraclass = " $options[class]";
  614. } else {
  615. $extraclass = '';
  616. }
  617. if (!ctype_alnum($username)) {
  618. $target = "{$ABSWEB}user.php?user=" . urlencode($username);
  619. if (isset($options['edit'])) {
  620. $target .= '&edit=1';
  621. }
  622. } else {
  623. $target = "{$ABSWEB}user.php/$username";
  624. if (isset($options['edit'])) {
  625. $target .= '?edit=1';
  626. }
  627. }
  628. $open_a = "<a $title href='$target' class='userlink$extraclass'>";
  629. $ret = '';
  630. if ((!isset($options['no_image']) || !$options['no_image'])) {
  631. $ret .= $open_a .
  632. mtrack_avatar($username, $options['size']) .
  633. '</a> ';
  634. }
  635. if (!isset($options['no_name']) || !$options['no_name']) {
  636. $dispuser = $username;
  637. if (strlen($dispuser) > 12) {
  638. if (preg_match("/^([^+]*)(\+.*)?@(.*)$/", $dispuser, $M)) {
  639. /* looks like an email address, try to shorten it in a reasonable way */
  640. $local = $M[1];
  641. $extra = $M[2];
  642. $domain = $M[3];
  643. if (strlen($extra)) {
  644. $local .= '...';
  645. }
  646. $dispuser = "$local@$domain";
  647. }
  648. }
  649. $ret .= "$open_a$dispuser</a>";
  650. }
  651. return $ret;
  652. }
  653. function mtrack_avatar($username, $size = 24)
  654. {
  655. global $ABSWEB;
  656. $id = urlencode($username);
  657. return "<img class='gravatar' width='$size' height='$size' src='{$ABSWEB}avatar.php?u=$id&amp;s=$size'>";
  658. }
  659. function mtrack_gravatar($email, $size = 24)
  660. {
  661. // d=identicon
  662. // d=monsterid
  663. // d=wavatar
  664. return "<img class='gravatar' width='$size' height='$size' src='http://www.gravatar.com/avatar/" . md5(strtolower($email)) . "?s=$size&amp;d=wavatar'>";
  665. }
  666. function mtrack_defrepo()
  667. {
  668. static $defrepo = null;
  669. if ($defrepo === null) {
  670. $defrepo = MTrackConfig::get('core', 'default.repo');
  671. if ($defrepo === null) {
  672. $defrepo = '';
  673. foreach (MTrackDB::q(
  674. 'select parent, shortname from repos order by shortname')
  675. ->fetchAll() as $row) {
  676. $defrepo = MTrackSCM::makeDisplayName($row);
  677. break;
  678. }
  679. } else if (strpos($defrepo, '/') === false) {
  680. $defrepo = 'default/' . $defrepo;
  681. }
  682. }
  683. return $defrepo;
  684. }
  685. function mtrack_changeset_url($cs, $repo = null)
  686. {
  687. global $ABSWEB;
  688. if ($repo instanceof MTrackRepo) {
  689. $p = $repo->getBrowseRootName() . '/';
  690. } elseif ($repo !== null) {
  691. if (strpos($repo, '/') === false) {
  692. $repo = "default/$repo";
  693. }
  694. $p = $repo . '/';
  695. } else {
  696. static $repos = null;
  697. if ($repos === null) {
  698. $repos = array();
  699. foreach (MTrackDB::q('select r.shortname as repo, p.shortname as proj from repos r left join project_repo_link l using (repoid) left join projects p using (projid) where parent is null or length(parent) = 0')->fetchAll(PDO::FETCH_ASSOC) as $row) {
  700. $r = $row['repo'];
  701. if ($row['proj']) {
  702. $repos[$row['proj']] = $r;
  703. }
  704. $repos[$row['repo']] = $r;
  705. }
  706. }
  707. $p = null;
  708. foreach ($repos as $a => $b) {
  709. if (!strncasecmp($cs, $a, strlen($a))) {
  710. $p = 'default/' . $b;
  711. $cs = substr($cs, strlen($a));
  712. break;
  713. }
  714. }
  715. if ($p === null) {
  716. $p = mtrack_defrepo();
  717. }
  718. $p .= '/';
  719. }
  720. return $ABSWEB . "changeset.php/$p$cs";
  721. }
  722. function mtrack_changeset($cs, $repo = null)
  723. {
  724. $display = $cs;
  725. if (strlen($display) > 12) {
  726. $display = substr($display, 0, 12);
  727. }
  728. $url = mtrack_changeset_url($cs, $repo);
  729. return "<a class='changesetlink' href='$url'>[$display]</a>";
  730. }
  731. function mtrack_branch($branch, $repo = null)
  732. {
  733. return "<span class='branchname'>$branch</span>";
  734. }
  735. function mtrack_wiki($pagename, $extras = array())
  736. {
  737. global $ABSWEB;
  738. if ($pagename instanceof MTrackWikiItem) {
  739. $wiki = $pagename;
  740. } else if (is_string($pagename)) {
  741. $wiki = null;//MTrackWikiItem::loadByPageName($pagename);
  742. } else {
  743. // FIXME: hinted data from reports
  744. throw new Exception("FIXME: wiki");
  745. }
  746. if ($wiki) {
  747. $pagename = $wiki->pagename;
  748. }
  749. $html = "<a class='wikilink'";
  750. if (isset($extras['#'])) {
  751. $anchor = '#' . $extras['#'];
  752. } else {
  753. $anchor = '';
  754. }
  755. $html .= " href=\"{$ABSWEB}wiki.php/$pagename$anchor\">";
  756. if (isset($extras['display'])) {
  757. $html .= htmlentities($extras['display'], ENT_QUOTES, 'utf-8');
  758. } else {
  759. $html .= htmlentities($pagename, ENT_QUOTES, 'utf-8');
  760. }
  761. $html .= "</a>";
  762. return $html;
  763. }
  764. function mtrack_ticket($no, $extras = array())
  765. {
  766. global $ABSWEB;
  767. if ($no instanceof MTrackIssue) {
  768. $tkt = $no;
  769. } else if (is_string($no) || is_int($no)) {
  770. static $cache = array();
  771. if ($no[0] == '#') {
  772. $no = substr($no, 1);
  773. }
  774. if (!isset($cache[$no])) {
  775. if (strlen($no) == 32) {
  776. $tkt = MTrackIssue::loadById($no);
  777. } else {
  778. $tkt = MTrackIssue::loadByNSIdent($no);
  779. }
  780. $cache[$no] = $tkt;
  781. } else {
  782. $tkt = $cache[$no];
  783. }
  784. } else {
  785. // FIXME: hinted data from reports
  786. $tkt = new stdClass;
  787. $tkt->tid = $no['ticket'];
  788. $tkt->summary = $no['summary'];
  789. if (isset($no['state'])) {
  790. $tkt->status = $no['state'];
  791. } elseif (isset($no['status'])) {
  792. $tkt->status = $no['status'];
  793. } elseif (isset($no['__status__'])) {
  794. $tkt->status = $no['__status__'];
  795. } else {
  796. $tkt->status = '';
  797. }
  798. }
  799. if ($tkt == NULL) {
  800. $tkt = new stdClass;
  801. $tkt->tid = $no;
  802. $tkt->summary = 'No such ticket';
  803. $tkt->status = 'No such ticket';
  804. }
  805. $html = "<a class='ticketlink";
  806. if ($tkt->status == 'closed') {
  807. $html .= ' completed';
  808. }
  809. if (!empty($tkt->nsident)) {
  810. $ident = $tkt->nsident;
  811. } else {
  812. $ident = $tkt->tid;
  813. }
  814. if (isset($extras['#'])) {
  815. $anchor = '#' . $extras['#'];
  816. } else {
  817. $anchor = '';
  818. }
  819. if (isset($extras['display']) && $tkt->tid == $extras['display']) {
  820. unset($extras['display']);
  821. }
  822. $html .= "' href=\"{$ABSWEB}ticket.php/$ident$anchor\">";
  823. if (isset($extras['display'])) {
  824. $html .= htmlentities($extras['display'], ENT_QUOTES, 'utf-8');
  825. } else {
  826. $html .= '#' . htmlentities($ident, ENT_QUOTES, 'utf-8');
  827. }
  828. $html .= "</a>";
  829. return $html;
  830. }
  831. function mtrack_tag($tag, $repo = null)
  832. {
  833. return "<span class='tagname'>$tag</span>";
  834. }
  835. function mtrack_component($comp)
  836. {
  837. global $ABSWEB;
  838. static $comps = null;
  839. if ($comps === null && preg_match("/^\d+$/", $comp)) {
  840. $comps = MTrackComponent::enumerate();
  841. }
  842. if (is_array($comps) && isset($comps[$comp])) {
  843. $comp = $comps[$comp]->name;
  844. }
  845. return "<span class='component'>" .
  846. htmlentities($comp, ENT_QUOTES, 'utf-8') . "</span>";
  847. }
  848. function mtrack_keyword($keyword)
  849. {
  850. global $ABSWEB;
  851. static $keywords = null;
  852. if ($keywords === null && preg_match("/^\d+$/", $keyword)) {
  853. $keywords = MTrackKeyword::enumerate();
  854. }
  855. if (is_array($keywords) && isset($keywords[$keyword])) {
  856. $keyword = $keywords[$keyword];
  857. }
  858. $kw = urlencode($keyword);
  859. return "<a class='keyword' href='{$ABSWEB}search.php?q=keyword%3A$kw'>$keyword</a>";
  860. }
  861. function mtrack_multi_select_box($name, $title, $items, $values = null)
  862. {
  863. $title = htmlentities($title, ENT_QUOTES, 'utf-8');
  864. $html = "<select id='$name' name='{$name}[]' multiple='multiple' title='$title' data-placeholder='$title'>";
  865. $in_group = null;
  866. foreach ($items as $k => $v) {
  867. $group = null;
  868. $label = null;
  869. if (is_array($v)) {
  870. /* option group item */
  871. list($label, $group) = $v;
  872. } else {
  873. $label = $v;
  874. }
  875. if ($in_group && $in_group != $group) {
  876. /* end of a group */
  877. $html .= "</optgroup>\n";
  878. $in_group = null;
  879. }
  880. if ($group && !$in_group) {
  881. $html .= "<optgroup label='" .
  882. htmlspecialchars($group, ENT_QUOTES, 'utf-8') .
  883. "'>\n";
  884. $in_group = $group;
  885. }
  886. $html .= "<option value='" .
  887. htmlspecialchars($k, ENT_QUOTES, 'utf-8') .
  888. "'";
  889. if (isset($values[$k])) {
  890. $html .= ' selected';
  891. }
  892. $html .= ">" . htmlentities($label, ENT_QUOTES, 'utf-8') . "</option>\n";
  893. }
  894. if ($in_group) {
  895. $html .= "</optgroup>\n";
  896. }
  897. return $html . "</select>";
  898. }
  899. function mtrack_select_box($name, $items, $value = null, $keyed = true,
  900. $placeholder = null)
  901. {
  902. if ($placeholder) {
  903. $placeholder = " data-placeholder='" .
  904. htmlentities($placeholder, ENT_QUOTES, 'utf-8') . "'";
  905. } else {
  906. $placeholder = '';
  907. }
  908. $html = "<select id='$name' name='$name'$placeholder>";
  909. foreach ($items as $k => $v) {
  910. $html .= "<option value='" .
  911. htmlspecialchars($keyed ? $k : $v, ENT_QUOTES, 'utf-8') .
  912. "'";
  913. if (($keyed && $value == $k) || (!$keyed && $value == $v)) {
  914. $html .= ' selected';
  915. }
  916. $html .= ">" . htmlentities($v, ENT_QUOTES, 'utf-8') . "</option>\n";
  917. }
  918. return $html . "</select>";
  919. }
  920. function mtrack_radio($name, $value, $curval)
  921. {
  922. $checked = $curval == $value ? " checked='checked'": '';
  923. return "<input type='radio' id='$value' name='$name' value='$value'$checked>";
  924. }
  925. function mtrack_diff($diffstr)
  926. {
  927. $nlines = 0;
  928. if (is_resource($diffstr)) {
  929. $lines = array();
  930. while (($line = fgets($diffstr)) !== false) {
  931. $lines[] = rtrim($line, "\r\n");
  932. }
  933. $diffstr = $lines;
  934. }
  935. if (is_string($diffstr)) {
  936. $abase = md5($diffstr);
  937. $diffstr = preg_split("/\r?\n/", $diffstr);
  938. } else {
  939. $abase = md5(join("\n", $diffstr));
  940. }
  941. /* we could use toggle() below, but it is much faster to determine
  942. * if we are hiding or showing based on a single variable than evaluating
  943. * that for each possible cell */
  944. $html = <<<HTML
  945. <button class='togglediffcopy btn' type='button'>Toggle Diff Line Numbers</button>
  946. HTML;
  947. $html .= "<table class='code diff'>";
  948. //$html = "<pre class='code diff'>";
  949. while (true) {
  950. if (!count($diffstr)) {
  951. break;
  952. }
  953. $line = array_shift($diffstr);
  954. $nlines++;
  955. if (!strncmp($line, '@@ ', 3)) {
  956. /* done with preamble */
  957. break;
  958. }
  959. $line = htmlspecialchars($line, ENT_QUOTES, 'utf-8');
  960. $line = "<tr class='meta'><td class='lineno'></td><td class='lineno'></td><td class='lineno'></td><td width='100%'>$line</tr>";
  961. $html .= $line . "\n";
  962. $line = null;
  963. }
  964. $lines = array(0, 0);
  965. $first = false;
  966. while ($line !== null) {
  967. $class = 'unmod';
  968. if (preg_match("/^@@\s+-(\pN+)(?:,\pN+)?\s+\+(\pN+)(?:,\pN+)?\s*@@/",
  969. $line, $M)) {
  970. $lines[0] = (int)$M[1] - 1;
  971. $lines[1] = (int)$M[2] - 1;
  972. $class = 'meta';
  973. $first = true;
  974. } elseif (strlen($line)) {
  975. if ($line[0] == '-') {
  976. $lines[0]++;
  977. $class = 'removed';
  978. } elseif ($line[0] == '+') {
  979. $lines[1]++;
  980. $class = 'added';
  981. } else {
  982. $lines[0]++;
  983. $lines[1]++;
  984. }
  985. } else {
  986. $lines[0]++;
  987. $lines[1]++;
  988. }
  989. $row = "<tr class='$class";
  990. if ($first) {
  991. $row .= ' first';
  992. }
  993. if ($class != 'meta' && $first) {
  994. $first = false;
  995. }
  996. $row .= "'>";
  997. switch ($class) {
  998. case 'meta':
  999. $line_info = '';
  1000. $row .= "<td class='lineno'></td><td class='lineno'></td>";
  1001. break;
  1002. case 'added':
  1003. $row .= "<td class='lineno'></td><td class='lineno'>" . $lines[1] . "</td>";
  1004. break;
  1005. case 'removed':
  1006. $row .= "<td class='lineno'>" . $lines[0] . "</td><td class='lineno'></td>";
  1007. break;
  1008. default:
  1009. $row .= "<td class='lineno'>" . $lines[0] . "</td><td class='lineno'>" . $lines[1] . "</td>";
  1010. }
  1011. $anchor = $abase . '.' . $nlines;
  1012. $row .= "<td class='linelink'><a name='$anchor'></a><a href='#$anchor' title='link to this line'>#</a></td>";
  1013. // deliberately don't inform it of the charset; we have no idea and we
  1014. // only really care about the obvious HTML metacharacters, not the entities
  1015. $line = htmlspecialchars($line);
  1016. $row .= "<td class='line' width='100%'>$line</td></tr>\n";
  1017. $html .= $row;
  1018. if (!count($diffstr)) {
  1019. break;
  1020. }
  1021. $line = array_shift($diffstr);
  1022. $nlines++;
  1023. }
  1024. if ($nlines == 0) {
  1025. return null;
  1026. }
  1027. $html .= "</table>";
  1028. return $html;
  1029. }
  1030. function _mtrack_admin_nav_add_cat(&$by_cat, $url) {
  1031. $cats = func_get_args();
  1032. array_shift($cats);
  1033. foreach ($cats as $cat) {
  1034. $by_cat[$cat][] = $url;
  1035. }
  1036. }
  1037. function mtrack_admin_nav()
  1038. {
  1039. global $ABSWEB;
  1040. $cat_titles = array(
  1041. 'user' => 'Users &amp; Groups',
  1042. 'repo' => 'Configure Repositories',
  1043. 'projects' => 'Projects &amp; Notifications',
  1044. 'tickets' => 'Configure Tickets',
  1045. 'logs' => 'Initial Setup &amp; Logs',
  1046. 'import' => 'Import',
  1047. );
  1048. $by_cat = array();
  1049. $add_cat = '_mtrack_admin_nav_add_cat';
  1050. if (MTrackACL::hasAnyRights('Projects', 'modify')) {
  1051. $add_cat($by_cat, "<a href='{$ABSWEB}admin/project.php'><i class='icon-envelope'></i> Projects and Groups</a>", 'user');
  1052. }
  1053. if (MTrackACL::hasAnyRights('Enumerations', 'modify')) {
  1054. $eurl = $ABSWEB . 'admin/enum.php';
  1055. $add_cat($by_cat, "<a href='$eurl/Priority'><i class='icon-list'></i> Priority</a>", 'tickets');
  1056. $add_cat($by_cat, "<a href='$eurl/TicketState'><i class='icon-list'></i> States</a>", 'tickets');
  1057. $add_cat($by_cat, "<a href='$eurl/Severity'><i class='icon-list'></i> Severity</a>", 'tickets');
  1058. $add_cat($by_cat, "<a href='$eurl/Resolution'><i class='icon-list'></i> Resolution</a>", 'tickets');
  1059. $add_cat($by_cat, "<a href='$eurl/Classification'><i class='icon-list'></i> Classification</a>", 'tickets');
  1060. $add_cat($by_cat, "<a href='{$ABSWEB}admin/customfield.php'><i class='icon-list'></i> Custom Fields</a>", 'tickets');
  1061. }
  1062. if (MTrackACL::hasAnyRights('Components', 'modify')) {
  1063. $add_cat($by_cat, "<a href='{$ABSWEB}admin/component.php'><i class='icon-list'></i> Components</a>", 'projects');
  1064. }
  1065. if (MTrackACL::hasAnyRights('Browser', 'modify')) {
  1066. $add_cat($by_cat, "<a href='{$ABSWEB}admin/repo.php'><i class='icon-file'></i> Repositories</a>", 'repo');
  1067. }
  1068. if (MTrackACL::hasAllRights('User', 'modify')) {
  1069. $add_cat($by_cat, "<a href='{$ABSWEB}admin/auth.php'><i class='icon-lock'></i> Authentication</a>", 'logs');
  1070. $add_cat($by_cat, "<a href='{$ABSWEB}admin/user.php'><i class='icon-user'></i> Users</a>", 'user');
  1071. }
  1072. if (MTrackACL::hasAnyRights('Tickets', 'create')) {
  1073. $add_cat($by_cat, "<a class='btn' href='{$ABSWEB}admin/importcsv.php'><i class='icon-upload'></i> Import CSV</a>", 'import');
  1074. }
  1075. if (MTrackACL::hasAllRights('Browser', 'modify')) {
  1076. $add_cat($by_cat, "<a href='{$ABSWEB}admin/logs.php'><i class='icon-cog'></i> Indexer logs</a>", 'logs');
  1077. }
  1078. /* there should be an easier way to figure this out, but there's
  1079. * no guaranteed way with PHP */
  1080. $here = preg_replace('{^.*/admin/}', '', $_SERVER['REQUEST_URI']);
  1081. echo "<div class='well' id='adminnav'>";
  1082. echo "<ul class='nav nav-list'>\n";
  1083. foreach ($cat_titles as $cat => $title) {
  1084. $links = $by_cat[$cat];
  1085. if (count($links) == 0) {
  1086. continue;
  1087. }
  1088. echo "<li class='nav-header'>$title</li>";
  1089. foreach ($links as $link) {
  1090. $class = '';
  1091. if (preg_match("{href='.*/admin/(.*?)'}", $link, $M)) {
  1092. $there = $M[1];
  1093. if ($here == $there) {
  1094. $class = " class='active'";
  1095. }
  1096. }
  1097. echo "<li$class>$link</li>\n";
  1098. }
  1099. }
  1100. echo "</ul>";
  1101. echo "</div>";
  1102. }
  1103. function mtrack_mime_detect($filename, $namehint = null)
  1104. {
  1105. /* does config tell us how to decide mimetype */
  1106. $detector = MTrackConfig::get('core', 'mimetype_detect');
  1107. /* if detector is blank, we'll try to figure out which one to use */
  1108. if (empty($detector)) {
  1109. if (function_exists('finfo_open')) {
  1110. $detector = 'fileinfo';
  1111. } elseif (function_exists('mime_content_type')) {
  1112. $detector = 'mime_magic';
  1113. } else {
  1114. $detector = 'file';
  1115. }
  1116. }
  1117. /* use detector or all mimetypes will be blank */
  1118. if ($detector === 'fileinfo') {
  1119. if (defined('FILEINFO_MIME_TYPE')) {
  1120. $fileinfo = finfo_open(FILEINFO_MIME_TYPE);
  1121. } else {
  1122. $magic = MTrackConfig::get('core', 'mime.magic');
  1123. if (strlen($magic)) {
  1124. $fileinfo = finfo_open(FILEINFO_MIME, $magic);
  1125. } else {
  1126. $fileinfo = finfo_open(FILEINFO_MIME);
  1127. }
  1128. }
  1129. $mimetype = finfo_file($fileinfo, $filename);
  1130. finfo_close($fileinfo);
  1131. } elseif ($detector === 'mime_magic') {
  1132. $mimetype = mime_content_type($filename);
  1133. } elseif (PHP_OS != 'SunOS') {
  1134. $mimetype = shell_exec("file -b --mime " . escapeshellarg($filename));
  1135. } else {
  1136. $mimetype = 'application/octet-stream';
  1137. }
  1138. $mimetype = trim(preg_replace("/\s*;.*$/", '', $mimetype));
  1139. if (empty($mimetype)) {
  1140. $mimetype = 'application/octet-stream';
  1141. }
  1142. if ($mimetype == 'application/octet-stream') {
  1143. if ($namehint === null) {
  1144. $namehint = $filename;
  1145. }
  1146. $pi = pathinfo($namehint);
  1147. switch (strtolower($pi['extension'])) {
  1148. case 'bin': return 'application/octet-stream';
  1149. case 'exe': return 'application/octet-stream';
  1150. case 'dll': return 'application/octet-stream';
  1151. case 'iso': return 'application/octet-stream';
  1152. case 'so': return 'application/octet-stream';
  1153. case 'a': return 'application/octet-stream';
  1154. case 'lib': return 'application/octet-stream';
  1155. case 'pdf': return 'application/pdf';
  1156. case 'ps': return 'application/postscript';
  1157. case 'ai': return 'application/postscript';
  1158. case 'eps': return 'application/postscript';
  1159. case 'ppt': return 'application/vnd.ms-powerpoint';
  1160. case 'xls': return 'application/vnd.ms-excel';
  1161. case 'tiff': return 'image/tiff';
  1162. case 'tif': return 'image/tiff';
  1163. case 'wbmp': return 'image/vnd.wap.wbmp';
  1164. case 'png': return 'image/png';
  1165. case 'gif': return 'image/gif';
  1166. case 'jpg': return 'image/jpeg';
  1167. case 'jpeg': return 'image/jpeg';
  1168. case 'ico': return 'image/x-icon';
  1169. case 'bmp': return 'image/bmp';
  1170. case 'css': return 'text/css';
  1171. case 'htm': return 'text/html';
  1172. case 'html': return 'text/html';
  1173. case 'txt': return 'text/plain';
  1174. case 'xml': return 'text/xml';
  1175. case 'eml': return 'message/rfc822';
  1176. case 'asc': return 'text/plain';
  1177. case 'rtf': return 'application/rtf';
  1178. case 'wml': return 'text/vnd.wap.wml';
  1179. case 'wmls': return 'text/vnd.wap.wmlscript';
  1180. case 'gtar': return 'application/x-gtar';
  1181. case 'gz': return 'application/x-gzip';
  1182. case 'tgz': return 'application/x-gzip';
  1183. case 'tar': return 'application/x-tar';
  1184. case 'zip': return 'application/zip';
  1185. case 'sql': return 'text/plain';
  1186. }
  1187. // if the file is ascii, then treat it as text/plain
  1188. $fp = fopen($filename, 'rb');
  1189. $mimetype = 'text/plain';
  1190. do {
  1191. $x = fread($fp, 8192);
  1192. if (!strlen($x)) break;
  1193. if (preg_match('/([\x80-\xff])/', $x, $M)) {
  1194. $mimetype = 'application/octet-stream';
  1195. break;
  1196. }
  1197. } while (true);
  1198. $fp = null;
  1199. }
  1200. return $mimetype;
  1201. }
  1202. function mtrack_run_tool($toolname, $mode, $args = null)
  1203. {
  1204. global $FORKS;
  1205. $tool = MTrackConfig::get('tools', $toolname);
  1206. if (!strlen($tool)) {
  1207. $tool = $toolname;
  1208. }
  1209. if (PHP_OS == 'Windows' && strpos($tool, ' ') !== false) {
  1210. $tool = '"' . $tool . '"';
  1211. }
  1212. $cmd = $tool;
  1213. if (is_array($args)) {
  1214. foreach ($args as $arg) {
  1215. if (is_array($arg)) {
  1216. foreach ($arg as $a) {
  1217. $cmd .= ' ' . escapeshellarg($a);
  1218. }
  1219. } else {
  1220. $cmd .= ' ' . escapeshellarg($arg);
  1221. }
  1222. }
  1223. }
  1224. if (!isset($FORKS[$cmd])) {
  1225. $FORKS[$cmd] = 0;
  1226. }
  1227. $FORKS[$cmd]++;
  1228. if (false) {
  1229. if (php_sapi_name() == 'cli') {
  1230. echo $cmd, "\n";
  1231. } else {
  1232. error_log($cmd);
  1233. echo htmlentities($cmd) . "<br>\n";
  1234. }
  1235. }
  1236. # $log = fopen('/var/tmp/mtrack.popen.log', 'a');
  1237. # fwrite($log, date('Y-m-d H:i:s') . " $cmd\n");
  1238. # fclose($log);
  1239. switch ($mode) {
  1240. case 'read': return popen($cmd, 'r');
  1241. case 'write': return popen($cmd, 'w');
  1242. case 'string': return stream_get_contents(popen($cmd, 'r'));
  1243. case 'proc':
  1244. $pipedef = array(
  1245. 0 => array('pipe', 'r'),
  1246. 1 => array('pipe', 'w'),
  1247. 2 => array('pipe', 'w'),
  1248. );
  1249. $proc = proc_open($cmd, $pipedef, $pipes);
  1250. return array($proc, $pipes);
  1251. }
  1252. }
  1253. if (php_sapi_name() != 'cli') {
  1254. set_exception_handler('mtrack_last_chance_saloon');
  1255. error_reporting(E_ALL);
  1256. ini_set('display_errors', false);
  1257. set_time_limit(300);
  1258. }