Overview

Packages

  • Chora
  • None

Classes

  • Chora
  • Chora_Exception
  • Chora_Readme_Collection
  • Chora_Renderer_File
  • Chora_Renderer_File_Html
  • Chora_Renderer_File_Plain
  • Chora_Test
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * Chora Base Class.
  4:  *
  5:  * Copyright 2000-2012 Horde LLC (http://www.horde.org/)
  6:  *
  7:  * See the enclosed file COPYING for license information (GPL). If you
  8:  * did not receive this file, see http://www.horde.org/licenses/gpl.
  9:  *
 10:  * @author  Anil Madhavapeddy <avsm@horde.org>
 11:  * @author  Michael Slusarz <slusarz@horde.org>
 12:  * @package Chora
 13:  */
 14: class Chora
 15: {
 16:     /**
 17:      * Cached data for isRestricted().
 18:      *
 19:      * @var array
 20:      */
 21:     static public $restricted;
 22: 
 23:     /**
 24:      * Cached data for readableTime().
 25:      *
 26:      * @var array
 27:      */
 28:     static public $rtcache;
 29: 
 30:     /**
 31:      * Cached data for formatDate().
 32:      *
 33:      * @var string
 34:      */
 35:     static public $fdcache;
 36: 
 37:     /**
 38:      * Create the breadcrumb directory listing.
 39:      *
 40:      * @param string $where  The current filepath.
 41:      * @param string $onb    If not null, the branch to add to the generated
 42:      *                       URLs.
 43:      *
 44:      * @return string  The directory string.
 45:      */
 46:     static public function whereMenu($where, $onb = null)
 47:     {
 48:         $bar = '';
 49:         $dirs = explode('/', $where);
 50:         $dir_count = count($dirs) - 1;
 51: 
 52:         $path = '';
 53:         foreach ($dirs as $i => $dir) {
 54:             if (!empty($path)) {
 55:                 $path .= '/';
 56:             }
 57:             $path .= $dir;
 58:             if (!empty($dir)) {
 59:                 $url = self::url('browsedir', $path . ($i == $dir_count && !$GLOBALS['atdir'] ? '' : '/'));
 60:                 if (!empty($onb)) {
 61:                     $url = $url->add('onb', $onb);
 62:                 }
 63:                 $bar .= '/ <a href="' . $url . '">' . $GLOBALS['injector']->getInstance('Horde_Core_Factory_TextFilter')->filter($dir, 'space2html', array('encode' => true, 'encode_all' => true)) . '</a> ';
 64:             }
 65:         }
 66: 
 67:         return $bar;
 68:     }
 69: 
 70:     /**
 71:      * Output an error page.
 72:      *
 73:      * @param string $message  The verbose error message to be displayed.
 74:      * @param string $code     The HTTP error number (and optional text), for
 75:      *                         sending 404s or other codes if appropriate.
 76:      */
 77:     static public function fatal($message, $code = null)
 78:     {
 79:         global $notification, $registry;
 80: 
 81:         if (is_a($message, 'Horde_Vcs_Exception')) {
 82:             $message = $message->getMessage();
 83:         }
 84: 
 85:         if ($code) {
 86:             header('HTTP/1.0 ' . $code);
 87:         }
 88: 
 89:         // Make sure we are in Chora scope.
 90:         $registry->pushApp('chora');
 91: 
 92:         $notification->push($message, 'horde.error');
 93:         require $registry->get('templates', 'horde') . '/common-header.inc';
 94:         require CHORA_TEMPLATES . '/menu.inc';
 95:         require $registry->get('templates', 'horde') . '/common-footer.inc';
 96:         exit;
 97:     }
 98: 
 99:     /**
100:      * Generate a URL that links into Chora.
101:      *
102:      * @param string $script  Name of the Chora script to link into.
103:      * @param string $uri     The path being browsed.
104:      * @param array $args     Key/value pair of any GET parameters to append.
105:      * @param string $anchor  Anchor entity name.
106:      *
107:      * @return string  The URL, with session information if necessary.
108:      */
109:     static public function url($script, $uri = '', $args = array(),
110:                                $anchor = '')
111:     {
112:         $arglist = self::_getArgList($GLOBALS['acts'],
113:                                      $GLOBALS['defaultActs'],
114:                                      $args);
115:         $script .= '.php';
116: 
117:         if ($GLOBALS['conf']['options']['urls'] == 'rewrite') {
118:             switch ($script) {
119:             case 'browsefile.php':
120:             case 'browsedir.php':
121:                 if (substr($uri, 0, 1) == '/') {
122:                     $script = "browse$uri";
123:                 } else {
124:                     $script = "browse/$uri";
125:                 }
126:                 $script = urlencode(isset($args['rt']) ? $args['rt'] : $GLOBALS['acts']['rt']) . "/-/$script";
127:                 unset($arglist['rt']);
128:                 break;
129: 
130:             case 'patchsets.php':
131:                 if (!empty($args['ps'])) {
132:                     $script = urlencode(isset($args['rt']) ? $args['rt'] : $GLOBALS['acts']['rt']) . '/-/commit/' . $args['ps'];
133:                     unset($arglist['ps']);
134:                 } else {
135:                     $script .= '/' . $uri;
136:                 }
137:                 break;
138: 
139:             default:
140:                 $script .= '/' . $uri;
141:             }
142:         } elseif (!empty($uri)) {
143:             $arglist['f'] = $uri;
144:         }
145: 
146:         return Horde::url($script)->add($arglist)->setAnchor($anchor);
147:     }
148: 
149:     /**
150:      * Generates hidden form fields with all required parameters.
151:      *
152:      * @return string  The form fields, with session information if necessary.
153:      */
154:     static public function formInputs()
155:     {
156:         $arglist = self::_getArgList($GLOBALS['acts'], $GLOBALS['defaultActs'], array());
157: 
158:         $fields = Horde_Util::formInput();
159:         foreach ($arglist as $key => $val) {
160:             $fields .= '<input type="hidden" name="' . htmlspecialchars($key) . '" value="' . htmlspecialchars($val) . '" />';
161:         }
162: 
163:         return $fields;
164:     }
165: 
166:     /**
167:      * TODO
168:      */
169:     static protected function _getArgList($acts, $defaultActs, $args)
170:     {
171:         $differing = array();
172: 
173:         foreach ($acts as $key => $val) {
174:             if ($val != $defaultActs[$key]) {
175:                 $differing[$key] = $val;
176:             }
177:         }
178: 
179:         return array_merge($differing, $args);
180:     }
181: 
182:     /**
183:      * TODO
184:      */
185:     static public function checkPerms($key)
186:     {
187:         return (!$GLOBALS['injector']->getInstance('Horde_Perms')->exists('chora:sourceroots:' . $key) ||
188:                 $GLOBALS['injector']->getInstance('Horde_Perms')->hasPermission('chora:sourceroots:' . $key, $GLOBALS['registry']->getAuth(), Horde_Perms::READ | Horde_Perms::SHOW));
189:     }
190: 
191:     /**
192:      * Returns the entries of $sourceroots that the current user has access
193:      * to.
194:      *
195:      * @return array  The sourceroots that the current user has access to.
196:      */
197:     static public function sourceroots()
198:     {
199:         $arr = array();
200: 
201:         foreach ($GLOBALS['sourceroots'] as $key => $val) {
202:             if (self::checkPerms($key)) {
203:                 $arr[$key] = $val;
204:             }
205:         }
206: 
207:         return $arr;
208:     }
209: 
210:     /**
211:      * Generate a list of repositories available from this installation of
212:      * Chora.
213:      *
214:      * @return string  XHTML code representing links to the repositories.
215:      */
216:     static public function repositories()
217:     {
218:         $sourceroots = self::sourceroots();
219:         $num_repositories = count($sourceroots);
220: 
221:         if ($num_repositories == 1) {
222:             return '';
223:         }
224: 
225:         $arr = array();
226:         foreach ($sourceroots as $key => $val) {
227:             if ($GLOBALS['sourceroot'] != $key) {
228:                 $arr[] = '<option value="' . self::url('browsedir', '', array('rt' => $key)) . '">' . $val['name'] . '</option>';
229:             }
230:         }
231: 
232:         return '<form action="#" id="repository-picker">' .
233:             '<select onchange="location.href=this[this.selectedIndex].value">' .
234:             '<option value="">' . _("Change repositories:") . '</option>' .
235:             implode('', $arr) . '</select></form>';
236:     }
237: 
238:     /**
239:      * Pretty-print the checked out copy, using Horde_Mime_Viewer.
240:      *
241:      * @param string $mime_type  File extension of the checked out file.
242:      * @param resource $fp       File pointer to the head of the checked out
243:      *                           copy.
244:      *
245:      * @return mixed  The Horde_Mime_Viewer object which can be rendered or
246:      *                false on failure.
247:      */
248:     static public function pretty($mime_type, $fp)
249:     {
250:         $lns = '';
251:         while ($ln = fread($fp, 8192)) {
252:             $lns .= $ln;
253:         }
254: 
255:         $mime = new Horde_Mime_Part();
256:         $mime->setType($mime_type);
257:         $mime->setContents($lns);
258: 
259:         return $GLOBALS['injector']->getInstance('Horde_Core_Factory_MimeViewer')->create($mime);
260:     }
261: 
262:     /**
263:      * Check if the given item is restricted from being shown.
264:      *
265:      * @param string $where  The current file path.
266:      *
267:      * @return boolean  Is item allowed to be displayed?
268:      */
269:     static public function isRestricted($where)
270:     {
271:         // First check if the current user has access to this repository.
272:         if (!self::checkPerms($GLOBALS['sourceroot'])) {
273:             return true;
274:         }
275: 
276:         if (!isset(self::$restricted)) {
277:             $restricted = array();
278: 
279:             if (isset($GLOBALS['conf']['restrictions']) &&
280:                 is_array($GLOBALS['conf']['restrictions'])) {
281:                 $restricted = $GLOBALS['conf']['restrictions'];
282:             }
283: 
284:             foreach ($GLOBALS['sourceroots'] as $key => $val) {
285:                 if (($GLOBALS['sourceroot'] == $key) &&
286:                     isset($val['restrictions']) &&
287:                     is_array($val['restrictions'])) {
288:                     $restricted = array_merge($restricted, $val['restrictions']);
289:                     break;
290:                 }
291:             }
292: 
293:             self::$restricted = $restricted;
294:         }
295: 
296:         if (!empty($restricted)) {
297:             for ($i = 0; $i < count($restricted); ++$i) {
298:                 if (preg_match('|' . str_replace('|', '\|', $restricted[$i]) . '|', $where)) {
299:                     return true;
300:                 }
301:             }
302:         }
303: 
304:         return false;
305:     }
306: 
307:     /**
308:      * Generate the link used for various file-based views.
309:      *
310:      * @param string $where    The current file path.
311:      * @param string $current  The current view ('browsefile', 'patchsets',
312:      *                         'history', 'cvsgraph', or 'stats').
313:      *
314:      * @return array  An array of file view links.
315:      */
316:     static public function getFileViews($where, $current)
317:     {
318:         $views = ($current == 'browsefile')
319:             ? array('<em class="widget">' . _("Logs") . '</em>')
320:             : array(Horde::widget(self::url('browsefile', $where), _("Logs"), 'widget', '', '', _("_Logs")));
321: 
322:         if ($GLOBALS['VC']->hasFeature('patchsets')) {
323:             $views[] = ($current == 'patchsets')
324:                 ? '<em class="widget">' . _("Patchsets") . '</em>'
325:                 : Horde::widget(self::url('patchsets', $where), _("Patchsets"), 'widget', '', '', _("_Patchsets"));
326:         }
327: 
328:         if ($GLOBALS['VC']->hasFeature('branches')) {
329:             if (empty($GLOBALS['conf']['paths']['cvsgraph']) ||
330:                 !($GLOBALS['VC'] instanceof Horde_Vcs_Cvs)) {
331:                 $views[] = ($current == 'history')
332:                     ? '<em class="widget">' . _("Branches") . '</em>'
333:                     : Horde::widget(self::url('history', $where), _("Branches"), 'widget', '', '', _("_Branches"));
334:             } else {
335:                 $views[] = ($current == 'cvsgraph')
336:                     ? '<em class="widget">' . _("Branches") . '</em>'
337:                     : Horde::widget(self::url('cvsgraph', $where), _("Branches"), 'widget', '', '', _("_Branches"));
338:             }
339:         }
340: 
341:         $views[] = ($current == 'stats')
342:             ? '<em class="widget">' . _("Statistics") . '</em>'
343:             : Horde::widget(self::url('stats', $where), _("Statistics"), 'widget', '', '', _("_Statistics"));
344: 
345:         return _("View:") . ' ' . implode(' | ', $views);
346:     }
347: 
348:     /**
349:      * Return a list of tags for a given log entry.
350:      *
351:      * @param Horde_Vcs_Log $lg  The Horde_Vcs_Log object.
352:      * @param string $where      The current filepath.
353:      *
354:      * @return array  An array of linked tags.
355:      */
356:     static public function getTags($lg, $where)
357:     {
358:         $tags = array();
359: 
360:         foreach ($lg->getSymbolicBranches() as $symb => $bra) {
361:             $tags[] = self::url('browsefile', $where, array('onb' => $bra))->link() . htmlspecialchars($symb) . '</a>';
362:         }
363: 
364:         foreach ($lg->getTags() as $tag) {
365:             $tags[] = htmlspecialchars($tag);
366:         }
367: 
368:         return $tags;
369:     }
370: 
371:     /**
372:      * Return a text description of how long its been since the file
373:      * has been last modified.
374:      *
375:      * @param integer $date  Number of seconds since epoch we wish to display.
376:      * @param boolean $long  If true, display a more verbose date.
377:      *
378:      * @return string  The human-readable date.
379:      */
380:     static public function readableTime($date, $long = false)
381:     {
382:         /* Initialize popular variables. */
383:         if (!isset(self::$rtcache)) {
384:             $desc = array(
385:                 1 => array(_("second"), _("seconds")),
386:                 60 => array(_("minute"), _("minutes")),
387:                 3600 => array(_("hour"), _("hours")),
388:                 86400 => array(_("day"), _("days")),
389:                 604800 => array(_("week"), _("weeks")),
390:                 2628000 => array(_("month"), _("months")),
391:                 31536000 => array(_("year"), _("years"))
392:             );
393: 
394:             self::$rtcache = array(
395:                 'breaks' => array_keys($desc),
396:                 'desc' => $desc,
397:                 'time' => time(),
398:             );
399:         }
400: 
401:         $cache = self::$rtcache;
402:         $i = count($cache['breaks']);
403:         $secs = $cache['time'] - $date;
404: 
405:         if ($secs < 2) {
406:             return _("very little time");
407:         }
408: 
409:         while (--$i && $i && $cache['breaks'][$i] * 2 > $secs);
410: 
411:         $break = $cache['breaks'][$i];
412: 
413:         $val = intval($secs / $break);
414:         $retval = $val . ' ' . ($val > 1 ? $cache['desc'][$break][1] : $cache['desc'][$break][0]);
415:         if ($long && $i > 0) {
416:             $rest = $secs % $break;
417:             $break = $cache['breaks'][--$i];
418:             $rest = (int)($rest / $break);
419:             if ($rest > 0) {
420:                 $retval .= ', ' . $rest . ' ' . ($rest > 1 ? $cache['desc'][$break][1] : $cache['desc'][$break][0]);
421:             }
422:         }
423: 
424:         return $retval;
425:     }
426: 
427:     /**
428:      * Convert a commit-name into whatever the user wants.
429:      *
430:      * @param string $name  Account name.
431:      *
432:      * @return string  The transformed name.
433:      */
434:     static public function showAuthorName($name, $fullname = false)
435:     {
436:         try {
437:             $users = $GLOBALS['VC']->getUsers($GLOBALS['chora_conf']['cvsusers']);
438:             if (isset($users[$name])) {
439:                 return '<a href="mailto:' . htmlspecialchars($users[$name]['mail']) . '">' .
440:                     htmlspecialchars($fullname ? $users[$name]['name'] : $name) .
441:                     '</a>' . ($fullname ? ' <em>' . htmlspecialchars($name) . '</em>' : '');
442:             }
443:         } catch (Horde_Vcs_Exception $e) {}
444: 
445:         return htmlspecialchars($name);
446:     }
447: 
448:     static public function getAuthorEmail($name)
449:     {
450:         try {
451:             $users = $GLOBALS['VC']->getUsers($GLOBALS['chora_conf']['cvsusers']);
452:             if (isset($users[$name])) {
453:                 return $users[$name]['mail'];
454:             }
455:         } catch (Horde_Vcs_Exception $e) {}
456: 
457:         try {
458:             $parser = new Horde_Mail_Rfc822();
459:             $results = $parser->parseAddressList($name);
460:             if (count($results)) {
461:                 return $results[0]->mailbox . '@' . $results[0]->host;
462:             }
463:         } catch (Horde_Mail_Exception $e) {
464:             try {
465:                 if (preg_match('|<(\S+)>|', $name, $matches)) {
466:                     return self::getAuthorEmail($matches[1]);
467:                 }
468:             } catch (Horde_Mail_Exception $e){}
469:         }
470: 
471:         return $name;
472:     }
473: 
474: 
475:     /**
476:      * Return formatted date information.
477:      *
478:      * @param integer $date  Number of seconds since epoch we wish to display.
479:      *
480:      * @return string  The date formatted pursuant to Horde prefs.
481:      */
482:     static public function formatDate($date)
483:     {
484:         if (!isset(self::$fdcache)) {
485:             self::$fdcache = $GLOBALS['prefs']->getValue('date_format') .
486:                 ($GLOBALS['prefs']->getValue('twenty_four')
487:                  ? ' %H:%M'
488:                  : ' %I:%M %p');
489:         }
490: 
491:         return strftime(self::$fdcache, $date);
492:     }
493: 
494:     /**
495:      * Formats a log message.
496:      *
497:      * @param string $log  The log message text.
498:      *
499:      * @return string  The formatted message.
500:      */
501:     static public function formatLogMessage($log)
502:     {
503:         $log = $GLOBALS['injector']->getInstance('Horde_Core_Factory_TextFilter')->filter($log, 'text2html', array('parselevel' => Horde_Text_Filter_Text2html::MICRO));
504: 
505:         return (empty($GLOBALS['conf']['tickets']['regexp']) || empty($GLOBALS['conf']['tickets']['replacement']))
506:             ? $log
507:             : preg_replace($GLOBALS['conf']['tickets']['regexp'], $GLOBALS['conf']['tickets']['replacement'], $log);
508:     }
509: 
510: }
511: 
API documentation generated by ApiGen