Namespaces

  • Nette
    • Application
      • Diagnostics
      • Responses
      • Routers
      • UI
    • Caching
      • Storages
    • ComponentModel
    • Config
      • Extensions
    • Database
      • Diagnostics
      • Drivers
      • Reflection
      • Table
    • DI
      • Diagnostics
    • Diagnostics
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Latte
      • Macros
    • Loaders
    • Localization
    • Mail
    • Reflection
    • Security
      • Diagnostics
    • Templating
    • Utils
      • PhpGenerator
  • NetteModule
  • None
  • PHP

Classes

  • Debugger
  • FireLogger
  • Logger

Interfaces

  • IBarPanel
  • Overview
  • Namespace
  • Class
  • Tree
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the Nette Framework (http://nette.org)
  5:  *
  6:  * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
  7:  *
  8:  * For the full copyright and license information, please view
  9:  * the file license.txt that was distributed with this source code.
 10:  */
 11: 
 12: namespace Nette\Diagnostics;
 13: 
 14: use Nette;
 15: 
 16: 
 17: 
 18: /**
 19:  * Debugger: displays and logs errors.
 20:  *
 21:  * Behavior is determined by two factors: mode & output
 22:  * - modes: production / development
 23:  * - output: HTML / AJAX / CLI / other (e.g. XML)
 24:  *
 25:  * @author     David Grudl
 26:  */
 27: final class Debugger
 28: {
 29:     /** @var bool in production mode is suppressed any debugging output */
 30:     public static $productionMode;
 31: 
 32:     /** @var bool in console mode is omitted HTML output */
 33:     public static $consoleMode;
 34: 
 35:     /** @var int timestamp with microseconds of the start of the request */
 36:     public static $time;
 37: 
 38:     /** @var bool is AJAX request detected? */
 39:     private static $ajaxDetected;
 40: 
 41:     /** @var string  requested URI or command line */
 42:     public static $source;
 43: 
 44:     /** @var string URL pattern mask to open editor */
 45:     public static $editor = 'editor://open/?file=%file&line=%line';
 46: 
 47:     /** @var string command to open browser (use 'start ""' in Windows) */
 48:     public static $browser;
 49: 
 50:     /********************* Debugger::dump() ****************d*g**/
 51: 
 52:     /** @var int  how many nested levels of array/object properties display {@link Debugger::dump()} */
 53:     public static $maxDepth = 3;
 54: 
 55:     /** @var int  how long strings display {@link Debugger::dump()} */
 56:     public static $maxLen = 150;
 57: 
 58:     /** @var bool display location? {@link Debugger::dump()} */
 59:     public static $showLocation = FALSE;
 60: 
 61:     /********************* errors and exceptions reporting ****************d*g**/
 62: 
 63:     /** server modes {@link Debugger::enable()} */
 64:     const DEVELOPMENT = FALSE,
 65:         PRODUCTION = TRUE,
 66:         DETECT = NULL;
 67: 
 68:     /** @var BlueScreen */
 69:     public static $blueScreen;
 70: 
 71:     /** @var bool|int determines whether any error will cause immediate death; if integer that it's matched against error severity */
 72:     public static $strictMode = FALSE; // $immediateDeath
 73: 
 74:     /** @var bool disables the @ (shut-up) operator so that notices and warnings are no longer hidden */
 75:     public static $scream = FALSE;
 76: 
 77:     /** @var array of callbacks specifies the functions that are automatically called after fatal error */
 78:     public static $onFatalError = array();
 79: 
 80:     /** @var bool {@link Debugger::enable()} */
 81:     private static $enabled = FALSE;
 82: 
 83:     /** @var mixed {@link Debugger::tryError()} FALSE means catching is disabled */
 84:     private static $lastError = FALSE;
 85: 
 86:     /********************* logging ****************d*g**/
 87: 
 88:     /** @var Logger */
 89:     public static $logger;
 90: 
 91:     /** @var FireLogger */
 92:     public static $fireLogger;
 93: 
 94:     /** @var string name of the directory where errors should be logged; FALSE means that logging is disabled */
 95:     public static $logDirectory;
 96: 
 97:     /** @var string email to sent error notifications */
 98:     public static $email;
 99: 
100:     /** @deprecated */
101:     public static $mailer;
102: 
103:     /** @deprecated */
104:     public static $emailSnooze;
105: 
106:     /********************* debug bar ****************d*g**/
107: 
108:     /** @var Bar */
109:     public static $bar;
110: 
111:     /** @var DefaultBarPanel */
112:     private static $errorPanel;
113: 
114:     /** @var DefaultBarPanel */
115:     private static $dumpPanel;
116: 
117:     /********************* Firebug extension ****************d*g**/
118: 
119:     /** {@link Debugger::log()} and {@link Debugger::fireLog()} */
120:     const DEBUG = 'debug',
121:         INFO = 'info',
122:         WARNING = 'warning',
123:         ERROR = 'error',
124:         CRITICAL = 'critical';
125: 
126: 
127: 
128:     /**
129:      * Static class - cannot be instantiated.
130:      */
131:     final public function __construct()
132:     {
133:         throw new Nette\StaticClassException;
134:     }
135: 
136: 
137: 
138:     /**
139:      * Static class constructor.
140:      * @internal
141:      */
142:     public static function _init()
143:     {
144:         self::$time = microtime(TRUE);
145:         self::$consoleMode = PHP_SAPI === 'cli';
146:         self::$productionMode = self::DETECT;
147:         if (self::$consoleMode) {
148:             self::$source = empty($_SERVER['argv']) ? 'cli' : 'cli: ' . implode(' ', $_SERVER['argv']);
149:         } else {
150:             self::$ajaxDetected = isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest';
151:             if (isset($_SERVER['REQUEST_URI'])) {
152:                 self::$source = (isset($_SERVER['HTTPS']) && strcasecmp($_SERVER['HTTPS'], 'off') ? 'https://' : 'http://')
153:                     . (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : ''))
154:                     . $_SERVER['REQUEST_URI'];
155:             }
156:         }
157: 
158:         self::$logger = new Logger;
159:         self::$logDirectory = & self::$logger->directory;
160:         self::$email = & self::$logger->email;
161:         self::$mailer = & self::$logger->mailer;
162:         self::$emailSnooze = & Logger::$emailSnooze;
163: 
164:         self::$fireLogger = new FireLogger;
165: 
166:         self::$blueScreen = new BlueScreen;
167:         self::$blueScreen->addPanel(function($e) {
168:             if ($e instanceof Nette\Templating\FilterException) {
169:                 return array(
170:                     'tab' => 'Template',
171:                     'panel' => '<p><b>File:</b> ' . Helpers::editorLink($e->sourceFile, $e->sourceLine)
172:                     . '&nbsp; <b>Line:</b> ' . ($e->sourceLine ? $e->sourceLine : 'n/a') . '</p>'
173:                     . ($e->sourceLine ? '<pre>' . BlueScreen::highlightFile($e->sourceFile, $e->sourceLine) . '</pre>' : '')
174:                 );
175:             }
176:         });
177: 
178:         self::$bar = new Bar;
179:         self::$bar->addPanel(new DefaultBarPanel('time'));
180:         self::$bar->addPanel(new DefaultBarPanel('memory'));
181:         self::$bar->addPanel(self::$errorPanel = new DefaultBarPanel('errors')); // filled by _errorHandler()
182:         self::$bar->addPanel(self::$dumpPanel = new DefaultBarPanel('dumps')); // filled by barDump()
183:     }
184: 
185: 
186: 
187:     /********************* errors and exceptions reporting ****************d*g**/
188: 
189: 
190: 
191:     /**
192:      * Enables displaying or logging errors and exceptions.
193:      * @param  mixed         production, development mode, autodetection or IP address(es) whitelist.
194:      * @param  string        error log directory; enables logging in production mode, FALSE means that logging is disabled
195:      * @param  string        administrator email; enables email sending in production mode
196:      * @return void
197:      */
198:     public static function enable($mode = NULL, $logDirectory = NULL, $email = NULL)
199:     {
200:         error_reporting(E_ALL | E_STRICT);
201: 
202:         // production/development mode detection
203:         if (is_bool($mode)) {
204:             self::$productionMode = $mode;
205: 
206:         } elseif ($mode !== self::DETECT || self::$productionMode === NULL) { // IP addresses or computer names whitelist detection
207:             $mode = is_string($mode) ? preg_split('#[,\s]+#', $mode) : array($mode);
208:             $mode[] = '127.0.0.1';
209:             $mode[] = '::1';
210:             self::$productionMode = !in_array(isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : php_uname('n'), $mode, TRUE);
211:         }
212: 
213:         // logging configuration
214:         if (is_string($logDirectory)) {
215:             self::$logDirectory = realpath($logDirectory);
216:             if (self::$logDirectory === FALSE) {
217:                 throw new Nette\DirectoryNotFoundException("Directory '$logDirectory' is not found.");
218:             }
219:         } elseif ($logDirectory === FALSE) {
220:             self::$logDirectory = FALSE;
221: 
222:         } elseif (self::$logDirectory === NULL) {
223:             self::$logDirectory = defined('APP_DIR') ? APP_DIR . '/../log' : getcwd() . '/log';
224:         }
225:         if (self::$logDirectory) {
226:             ini_set('error_log', self::$logDirectory . '/php_error.log');
227:         }
228: 
229:         // php configuration
230:         if (function_exists('ini_set')) {
231:             ini_set('display_errors', !self::$productionMode); // or 'stderr'
232:             ini_set('html_errors', FALSE);
233:             ini_set('log_errors', FALSE);
234: 
235:         } elseif (ini_get('display_errors') != !self::$productionMode && ini_get('display_errors') !== (self::$productionMode ? 'stderr' : 'stdout')) { // intentionally ==
236:             throw new Nette\NotSupportedException('Function ini_set() must be enabled.');
237:         }
238: 
239:         if ($email) {
240:             if (!is_string($email)) {
241:                 throw new Nette\InvalidArgumentException('Email address must be a string.');
242:             }
243:             self::$email = $email;
244:         }
245: 
246:         if (!defined('E_DEPRECATED')) {
247:             define('E_DEPRECATED', 8192);
248:         }
249: 
250:         if (!defined('E_USER_DEPRECATED')) {
251:             define('E_USER_DEPRECATED', 16384);
252:         }
253: 
254:         if (!self::$enabled) {
255:             register_shutdown_function(array(__CLASS__, '_shutdownHandler'));
256:             set_exception_handler(array(__CLASS__, '_exceptionHandler'));
257:             set_error_handler(array(__CLASS__, '_errorHandler'));
258:             self::$enabled = TRUE;
259:         }
260:     }
261: 
262: 
263: 
264:     /**
265:      * Is Debug enabled?
266:      * @return bool
267:      */
268:     public static function isEnabled()
269:     {
270:         return self::$enabled;
271:     }
272: 
273: 
274: 
275:     /**
276:      * Logs message or exception to file (if not disabled) and sends email notification (if enabled).
277:      * @param  string|Exception
278:      * @param  int  one of constant Debugger::INFO, WARNING, ERROR (sends email), CRITICAL (sends email)
279:      * @return string logged error filename
280:      */
281:     public static function log($message, $priority = self::INFO)
282:     {
283:         if (self::$logDirectory === FALSE) {
284:             return;
285: 
286:         } elseif (!self::$logDirectory) {
287:             throw new Nette\InvalidStateException('Logging directory is not specified in Nette\Diagnostics\Debugger::$logDirectory.');
288:         }
289: 
290:         if ($message instanceof \Exception) {
291:             $exception = $message;
292:             $message = ($message instanceof Nette\FatalErrorException
293:                 ? 'Fatal error: ' . $exception->getMessage()
294:                 : get_class($exception) . ": " . $exception->getMessage())
295:                 . " in " . $exception->getFile() . ":" . $exception->getLine();
296: 
297:             $hash = md5($exception );
298:             $exceptionFilename = "exception-" . @date('Y-m-d-H-i-s') . "-$hash.html";
299:             foreach (new \DirectoryIterator(self::$logDirectory) as $entry) {
300:                 if (strpos($entry, $hash)) {
301:                     $exceptionFilename = $entry;
302:                     $saved = TRUE;
303:                     break;
304:                 }
305:             }
306:         }
307: 
308:         self::$logger->log(array(
309:             @date('[Y-m-d H-i-s]'),
310:             $message,
311:             self::$source ? ' @  ' . self::$source : NULL,
312:             !empty($exceptionFilename) ? ' @@  ' . $exceptionFilename : NULL
313:         ), $priority);
314: 
315:         if (!empty($exceptionFilename)) {
316:             $exceptionFilename = self::$logDirectory . '/' . $exceptionFilename;
317:             if (empty($saved) && $logHandle = @fopen($exceptionFilename, 'w')) {
318:                 ob_start(); // double buffer prevents sending HTTP headers in some PHP
319:                 ob_start(function($buffer) use ($logHandle) { fwrite($logHandle, $buffer); }, 4096);
320:                 self::$blueScreen->render($exception);
321:                 ob_end_flush();
322:                 ob_end_clean();
323:                 fclose($logHandle);
324:             }
325:             return strtr($exceptionFilename, '\\/', DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR);
326:         }
327:     }
328: 
329: 
330: 
331:     /**
332:      * Shutdown handler to catch fatal errors and execute of the planned activities.
333:      * @return void
334:      * @internal
335:      */
336:     public static function _shutdownHandler()
337:     {
338:         if (!self::$enabled) {
339:             return;
340:         }
341: 
342:         // fatal error handler
343:         static $types = array(
344:             E_ERROR => 1,
345:             E_CORE_ERROR => 1,
346:             E_COMPILE_ERROR => 1,
347:             E_PARSE => 1,
348:         );
349:         $error = error_get_last();
350:         if (isset($types[$error['type']])) {
351:             self::_exceptionHandler(new Nette\FatalErrorException($error['message'], 0, $error['type'], $error['file'], $error['line'], NULL));
352:         }
353: 
354:         // debug bar (require HTML & development mode)
355:         if (self::$bar && !self::$productionMode && self::isHtmlMode()) {
356:             self::$bar->render();
357:         }
358:     }
359: 
360: 
361: 
362:     /**
363:      * Handler to catch uncaught exception.
364:      * @param  \Exception
365:      * @return void
366:      * @internal
367:      */
368:     public static function _exceptionHandler(\Exception $exception)
369:     {
370:         if (!headers_sent()) { // for PHP < 5.2.4
371:             $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.1';
372:             header($protocol . ' 500', TRUE, 500);
373:         }
374: 
375:         try {
376:             if (self::$productionMode) {
377:                 try {
378:                     self::log($exception, self::ERROR);
379:                 } catch (\Exception $e) {
380:                     echo 'FATAL ERROR: unable to log error';
381:                 }
382: 
383:                 if (self::$consoleMode) {
384:                     echo "ERROR: the server encountered an internal error and was unable to complete your request.\n";
385: 
386:                 } elseif (self::isHtmlMode()) {
387:                     require __DIR__ . '/templates/error.phtml';
388:                 }
389: 
390:             } else {
391:                 if (self::$consoleMode) { // dump to console
392:                     echo "$exception\n";
393:                     if ($file = self::log($exception)) {
394:                         echo "(stored in $file)\n";
395:                         if (self::$browser) {
396:                             exec(self::$browser . ' ' . escapeshellarg($file));
397:                         }
398:                     }
399: 
400:                 } elseif (self::isHtmlMode()) { // dump to browser
401:                     self::$blueScreen->render($exception);
402:                     if (self::$bar) {
403:                         self::$bar->render();
404:                     }
405: 
406:                 } elseif (!self::fireLog($exception, self::ERROR)) { // AJAX or non-HTML mode
407:                     $file = self::log($exception);
408:                     if (!headers_sent()) {
409:                         header("X-Nette-Error-Log: $file");
410:                     }
411:                 }
412:             }
413: 
414:             foreach (self::$onFatalError as $handler) {
415:                 call_user_func($handler, $exception);
416:             }
417: 
418:         } catch (\Exception $e) {
419:             if (self::$productionMode) {
420:                 echo self::isHtmlMode() ? '<meta name=robots content=noindex>FATAL ERROR' : 'FATAL ERROR';
421:             } else {
422:                 echo "FATAL ERROR: thrown ", get_class($e), ': ', $e->getMessage(),
423:                     "\nwhile processing ", get_class($exception), ': ', $exception->getMessage(), "\n";
424:             }
425:         }
426: 
427:         self::$enabled = FALSE; // un-register shutdown function
428:         exit(255);
429:     }
430: 
431: 
432: 
433:     /**
434:      * Handler to catch warnings and notices.
435:      * @param  int    level of the error raised
436:      * @param  string error message
437:      * @param  string file that the error was raised in
438:      * @param  int    line number the error was raised at
439:      * @param  array  an array of variables that existed in the scope the error was triggered in
440:      * @return bool   FALSE to call normal error handler, NULL otherwise
441:      * @throws Nette\FatalErrorException
442:      * @internal
443:      */
444:     public static function _errorHandler($severity, $message, $file, $line, $context)
445:     {
446:         if (self::$scream) {
447:             error_reporting(E_ALL | E_STRICT);
448:         }
449: 
450:         if (self::$lastError !== FALSE && ($severity & error_reporting()) === $severity) { // tryError mode
451:             self::$lastError = new \ErrorException($message, 0, $severity, $file, $line);
452:             return NULL;
453:         }
454: 
455:         if ($severity === E_RECOVERABLE_ERROR || $severity === E_USER_ERROR) {
456:             throw new Nette\FatalErrorException($message, 0, $severity, $file, $line, $context);
457: 
458:         } elseif (($severity & error_reporting()) !== $severity) {
459:             return FALSE; // calls normal error handler to fill-in error_get_last()
460: 
461:         } elseif (!self::$productionMode && (is_bool(self::$strictMode) ? self::$strictMode : ((self::$strictMode & $severity) === $severity))) {
462:             self::_exceptionHandler(new Nette\FatalErrorException($message, 0, $severity, $file, $line, $context));
463:         }
464: 
465:         static $types = array(
466:             E_WARNING => 'Warning',
467:             E_COMPILE_WARNING => 'Warning', // currently unable to handle
468:             E_USER_WARNING => 'Warning',
469:             E_NOTICE => 'Notice',
470:             E_USER_NOTICE => 'Notice',
471:             E_STRICT => 'Strict standards',
472:             E_DEPRECATED => 'Deprecated',
473:             E_USER_DEPRECATED => 'Deprecated',
474:         );
475: 
476:         $message = 'PHP ' . (isset($types[$severity]) ? $types[$severity] : 'Unknown error') . ": $message";
477:         $count = & self::$errorPanel->data["$message|$file|$line"];
478: 
479:         if ($count++) { // repeated error
480:             return NULL;
481: 
482:         } elseif (self::$productionMode) {
483:             self::log("$message in $file:$line", self::ERROR);
484:             return NULL;
485: 
486:         } else {
487:             $ok = self::fireLog(new \ErrorException($message, 0, $severity, $file, $line), self::WARNING);
488:             return !self::isHtmlMode() || (!self::$bar && !$ok) ? FALSE : NULL;
489:         }
490: 
491:         return FALSE; // call normal error handler
492:     }
493: 
494: 
495: 
496:     /**
497:      * Handles exception thrown in __toString().
498:      * @param  \Exception
499:      * @return void
500:      */
501:     public static function toStringException(\Exception $exception)
502:     {
503:         if (self::$enabled) {
504:             self::_exceptionHandler($exception);
505:         } else {
506:             trigger_error($exception->getMessage(), E_USER_ERROR);
507:         }
508:     }
509: 
510: 
511: 
512:     /**
513:      * Starts catching potential errors/warnings.
514:      * @return void
515:      */
516:     public static function tryError()
517:     {
518:         if (!self::$enabled && self::$lastError === FALSE) {
519:             set_error_handler(array(__CLASS__, '_errorHandler'));
520:         }
521:         self::$lastError = NULL;
522:     }
523: 
524: 
525: 
526:     /**
527:      * Returns catched error/warning message.
528:      * @param  \ErrorException  catched error
529:      * @return bool
530:      */
531:     public static function catchError(& $error)
532:     {
533:         if (!self::$enabled && self::$lastError !== FALSE) {
534:             restore_error_handler();
535:         }
536:         $error = self::$lastError;
537:         self::$lastError = FALSE;
538:         return (bool) $error;
539:     }
540: 
541: 
542: 
543:     /********************* useful tools ****************d*g**/
544: 
545: 
546: 
547:     /**
548:      * Dumps information about a variable in readable format.
549:      * @param  mixed  variable to dump
550:      * @param  bool   return output instead of printing it? (bypasses $productionMode)
551:      * @return mixed  variable itself or dump
552:      */
553:     public static function dump($var, $return = FALSE)
554:     {
555:         if (!$return && self::$productionMode) {
556:             return $var;
557:         }
558: 
559:         $output = "<pre class=\"nette-dump\">" . Helpers::htmlDump($var) . "</pre>\n";
560: 
561:         if (!$return) {
562:             $trace = debug_backtrace();
563:             $i = !isset($trace[1]['class']) && isset($trace[1]['function']) && $trace[1]['function'] === 'dump' ? 1 : 0;
564:             if (isset($trace[$i]['file'], $trace[$i]['line']) && is_file($trace[$i]['file'])) {
565:                 $lines = file($trace[$i]['file']);
566:                 preg_match('#dump\((.*)\)#', $lines[$trace[$i]['line'] - 1], $m);
567:                 $output = substr_replace(
568:                     $output,
569:                     ' title="' . htmlspecialchars((isset($m[0]) ? "$m[0] \n" : '') . "in file {$trace[$i]['file']} on line {$trace[$i]['line']}") . '"',
570:                     4, 0);
571: 
572:                 if (self::$showLocation) {
573:                     $output = substr_replace(
574:                         $output,
575:                         ' <small>in ' . Helpers::editorLink($trace[$i]['file'], $trace[$i]['line']) . ":{$trace[$i]['line']}</small>",
576:                         -8, 0);
577:                 }
578:             }
579:         }
580: 
581:         if (self::$consoleMode) {
582:             $output = htmlspecialchars_decode(strip_tags($output), ENT_QUOTES);
583:         }
584: 
585:         if ($return) {
586:             return $output;
587: 
588:         } else {
589:             echo $output;
590:             return $var;
591:         }
592:     }
593: 
594: 
595: 
596:     /**
597:      * Starts/stops stopwatch.
598:      * @param  string  name
599:      * @return float   elapsed seconds
600:      */
601:     public static function timer($name = NULL)
602:     {
603:         static $time = array();
604:         $now = microtime(TRUE);
605:         $delta = isset($time[$name]) ? $now - $time[$name] : 0;
606:         $time[$name] = $now;
607:         return $delta;
608:     }
609: 
610: 
611: 
612:     /**
613:      * Dumps information about a variable in Nette Debug Bar.
614:      * @param  mixed  variable to dump
615:      * @param  string optional title
616:      * @return mixed  variable itself
617:      */
618:     public static function barDump($var, $title = NULL)
619:     {
620:         if (!self::$productionMode) {
621:             $dump = array();
622:             foreach ((is_array($var) ? $var : array('' => $var)) as $key => $val) {
623:                 $dump[$key] = Helpers::clickableDump($val);
624:             }
625:             self::$dumpPanel->data[] = array('title' => $title, 'dump' => $dump);
626:         }
627:         return $var;
628:     }
629: 
630: 
631: 
632:     /**
633:      * Sends message to FireLogger console.
634:      * @param  mixed   message to log
635:      * @return bool    was successful?
636:      */
637:     public static function fireLog($message)
638:     {
639:         if (!self::$productionMode) {
640:             return self::$fireLogger->log($message);
641:         }
642:     }
643: 
644: 
645: 
646:     private static function isHtmlMode()
647:     {
648:         return !self::$ajaxDetected && !self::$consoleMode
649:             && !preg_match('#^Content-Type: (?!text/html)#im', implode("\n", headers_list()));
650:     }
651: 
652: 
653: 
654:     /** @deprecated */
655:     public static function addPanel(IBarPanel $panel, $id = NULL)
656:     {
657:         return self::$bar->addPanel($panel, $id);
658:     }
659: 
660: }
661: 
Nette Framework 2.0beta2 API API documentation generated by ApiGen 2.3.0