Source Code

00001: <?php
00002: /**
00003:  * Copyright (c) 2009-2010, Laurent Laville <pear@laurent-laville.org>
00004:  *                          Bertrand Mansion <bmansion@mamasam.com>
00005:  *
00006:  * All rights reserved.
00007:  *
00008:  * Redistribution and use in source and binary forms, with or without
00009:  * modification, are permitted provided that the following conditions
00010:  * are met:
00011:  *
00012:  *     * Redistributions of source code must retain the above copyright
00013:  *       notice, this list of conditions and the following disclaimer.
00014:  *     * Redistributions in binary form must reproduce the above copyright
00015:  *       notice, this list of conditions and the following disclaimer in the
00016:  *       documentation and/or other materials provided with the distribution.
00017:  *     * Neither the name of the authors nor the names of its contributors
00018:  *       may be used to endorse or promote products derived from this software
00019:  *       without specific prior written permission.
00020:  *
00021:  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
00022:  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00023:  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
00024:  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
00025:  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
00026:  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
00027:  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
00028:  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
00029:  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
00030:  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00031:  * POSSIBILITY OF SUCH DAMAGE.
00032:  *
00033:  * PHP version 5
00034:  *
00035:  * @category Networking
00036:  * @package  Net_Growl
00037:  * @author   Laurent Laville <pear@laurent-laville.org>
00038:  * @author   Bertrand Mansion <bmansion@mamasam.com>
00039:  * @license  http://www.opensource.org/licenses/bsd-license.php  BSD
00040:  * @version  CVS: $Id:$
00041:  * @link     http://growl.laurent-laville.org/
00042:  * @since    File available since Release 0.9.0
00043:  */
00044:
00045: require_once 'Net/Growl/Exception.php';
00046:
00047:  // Lazy loading allowed by a custom __autoload function
00048: spl_autoload_register(array('Net_Growl', 'autoload'));
00049:
00050: // Converts standard error into exception
00051: set_error_handler(array('Net_Growl', 'errorHandler'));
00052:
00053: /**
00054:  * Sends notifications to {@link http://growl.info Growl}
00055:  *
00056:  * This package makes it possible to easily send a notification from
00057:  * your PHP script to {@link http://growl.info Growl}.
00058:  *
00059:  * Growl is a global notification system for Mac OS X.
00060:  * Any application can send a notification to Growl, which will display
00061:  * an attractive message on your screen. Growl currently works with a
00062:  * growing number of applications.
00063:  *
00064:  * The class provides the following capabilities:
00065:  * - Register your PHP application in Growl.
00066:  * - Let Growl know what kind of notifications to expect.
00067:  * - Notify Growl.
00068:  * - Set a maximum number of notifications to be displayed (beware the loops !).
00069:  *
00070:  * @category Networking
00071:  * @package  Net_Growl
00072:  * @author   Laurent Laville <pear@laurent-laville.org>
00073:  * @author   Bertrand Mansion <bmansion@mamasam.com>
00074:  * @license  http://www.opensource.org/licenses/bsd-license.php  BSD
00075:  * @version  Release: @package_version@
00076:  * @link     http://growl.laurent-laville.org/
00077:  * @link     http://growl.info Growl Homepage
00078:  * @since    Class available since Release 0.9.0
00079:  */
00080: class Net_Growl
00081: {
00082:     /**
00083:      * Growl default UDP port
00084:      */
00085:     const UDP_PORT = 9887;
00086:
00087:     /**
00088:      * Growl default GNTP port
00089:      */
00090:     const GNTP_PORT = 23053;
00091:
00092:     /**
00093:      * Growl priorities
00094:      */
00095:     const PRIORITY_LOW = -2;
00096:     const PRIORITY_MODERATE = -1;
00097:     const PRIORITY_NORMAL = 0;
00098:     const PRIORITY_HIGH = 1;
00099:     const PRIORITY_EMERGENCY = 2;
00100:
00101:     /**
00102:      * PHP application object
00103:      *
00104:      * This is usually a Net_Growl_Application object but can really be
00105:      * any other object as long as Net_Growl_Application methods are
00106:      * implemented.
00107:      *
00108:      * @var object
00109:      */
00110:     private $_application;
00111:
00112:     /**
00113:      * Application is registered
00114:      * @var bool
00115:      */
00116:     protected $isRegistered = false;
00117:
00118:     /**
00119:      * Net_Growl connection options
00120:      * @var array
00121:      */
00122:     protected $options = array(
00123:         'host' => '127.0.0.1',
00124:         'port' => self::UDP_PORT,
00125:         'protocol' => 'udp',
00126:         'timeout' => 30,
00127:         'context' => array(),
00128:         'passwordHashAlgorithm' => 'MD5',
00129:         'encryptionAlgorithm' => 'NONE',
00130:         'debug' => false
00131:     );
00132:
00133:     /**
00134:      * Current number of notification being displayed on user desktop
00135:      * @var int
00136:      */
00137:     protected $growlNotificationCount = 0;
00138:
00139:     /**
00140:      * Maximum number of notification to be displayed on user desktop
00141:      * @var int
00142:      */
00143:     private $_growlNotificationLimit = 0;
00144:
00145:     /**
00146:      * Handle to the log file.
00147:      * @var resource
00148:      * @since 2.0.0b2
00149:      */
00150:     private $_fp = false;
00151:
00152:     /**
00153:      * Notification callback results
00154:      *
00155:      * @var array
00156:      * @since 2.0.0b2
00157:      */
00158:     protected $growlNotificationCallback = array();
00159:
00160:     /**
00161:      * Notification unique instance
00162:      * @var   object
00163:      * @since 2.1.0
00164:      * @see   singleton, reset
00165:      */
00166:     protected static $instance = null;
00167:
00168:     /**
00169:      * Singleton
00170:      *
00171:      * Makes sure there is only one Growl connection open.
00172:      *
00173:      * @param mixed  &$application  Can be either a Net_Growl_Application object
00174:      *                              or the application name string
00175:      * @param array  $notifications List of notification types
00176:      * @param string $password      (optional) Password for Growl
00177:      * @param array  $options       (optional) List of options : 'host', 'port',
00178:      *                              'protocol', 'timeout' for Growl socket server.
00179:      *                              'passwordHashAlgorithm', 'encryptionAlgorithm'
00180:      *                              to secure communications.
00181:      *                              'debug' to know what data are sent and received.
00182:      *
00183:      * @return object Net_Growl
00184:      * @throws Net_Growl_Exception if class handler does not exists
00185:      */
00186:     public static final function singleton(&$application, $notifications,
00187:         $password = '', $options = array()
00188:     ) {
00189:         if (self::$instance === null) {
00190:             if (isset($options['protocol'])) {
00191:                 if ($options['protocol'] == 'tcp') {
00192:                     $protocol = 'gntp';
00193:                 } else {
00194:                     $protocol = $options['protocol'];
00195:                 }
00196:             } else {
00197:                 $protocol = 'udp';
00198:             }
00199:             $class = 'Net_Growl_' . ucfirst($protocol);
00200:
00201:             if (class_exists($class, true)) {
00202:                 self::$instance = new $class(
00203:                     $application, $notifications, $password, $options
00204:                 );
00205:             } else {
00206:                 $message = 'Cannot find class "'.$class.'"';
00207:                 throw new Net_Growl_Exception($message);
00208:             }
00209:         }
00210:         return self::$instance;
00211:     }
00212:
00213:     /**
00214:      * Resettable Singleton Solution
00215:      *
00216:      * @return void
00217:      * @link http://sebastian-bergmann.de/archives/882-guid.html
00218:      *       Testing Code That Uses Singletons
00219:      * @since 2.1.0
00220:      */
00221:     public static final function reset()
00222:     {
00223:         self::$instance = null;
00224:     }
00225:
00226:     /**
00227:      * Constructor
00228:      *
00229:      * This method instantiate a new Net_Growl object and opens a socket connection
00230:      * to the specified Growl socket server.
00231:      * Currently, only UDP is supported by Growl.
00232:      * The constructor registers a shutdown function {@link Net_Growl::_Net_Growl()}
00233:      * that closes the socket if it is open.
00234:      *
00235:      * Example 1.
00236:      * <code>
00237:      * require_once 'Net/Growl.php';
00238:      *
00239:      * $notifications = array('Errors', 'Messages');
00240:      * $growl = Net_Growl::singleton('My application', $notification);
00241:      * $growl->notify( 'Messages',
00242:      *                 'My notification title',
00243:      *                 'My notification description');
00244:      * </code>
00245:      *
00246:      * @param mixed  &$application  Can be either a Net_Growl_Application object
00247:      *                              or the application name string
00248:      * @param array  $notifications (optional) List of notification types
00249:      * @param string $password      (optional) Password for Growl
00250:      * @param array  $options       (optional) List of options : 'host', 'port',
00251:      *                              'protocol', 'timeout' for Growl socket server.
00252:      *                              'passwordHashAlgorithm', 'encryptionAlgorithm'
00253:      *                              to secure communications.
00254:      *                              'debug' to know what data are sent and received.
00255:      *
00256:      * @return void
00257:      */
00258:     protected function __construct(&$application, $notifications = array(),
00259:         $password = '', $options = array()
00260:     ) {
00261:         foreach ($options as $k => $v) {
00262:             if (isset($this->options[$k])) {
00263:                 $this->options[$k] = $v;
00264:             }
00265:         }
00266:         $timeout = $this->options['timeout'];
00267:         if (!is_int($timeout)) {
00268:             // get default timeout (in seconds) for socket based streams.
00269:             $timeout = ini_get('default_socket_timeout');
00270:         }
00271:         if (!is_int($timeout)) {
00272:             // if default timeout not available on php.ini, then use this one
00273:             $timeout = 30;
00274:         }
00275:         $this->options['timeout'] = $timeout;
00276:
00277:         if (is_string($application)) {
00278:             if (isset($options['AppIcon'])) {
00279:                 $icon = $options['AppIcon'];
00280:             } else {
00281:                 $icon = '';
00282:             }
00283:             $this->_application = new Net_Growl_Application(
00284:                 $application, $notifications, $password, $icon
00285:             );
00286:         } elseif (is_object($application)) {
00287:             $this->_application = $application;
00288:         }
00289:
00290:         if (is_string($this->options['debug'])) {
00291:             $this->_fp = fopen($this->options['debug'], 'a');
00292:         }
00293:     }
00294:
00295:     /**
00296:      * Destructor
00297:      *
00298:      * @since 2.0.0b2
00299:      */
00300:     public function __destruct()
00301:     {
00302:         if (is_resource($this->_fp)) {
00303:             fclose($this->_fp);
00304:         }
00305:     }
00306:
00307:     /**
00308:      * Limit the number of notifications
00309:      *
00310:      * This method limits the number of notifications to be displayed on
00311:      * the Growl user desktop. By default, there is no limit. It is used
00312:      * mostly to prevent problem with notifications within loops.
00313:      *
00314:      * @param int $max Maximum number of notifications
00315:      *
00316:      * @return void
00317:      */
00318:     public function setNotificationLimit($max)
00319:     {
00320:         $this->_growlNotificationLimit = $max;
00321:     }
00322:
00323:     /**
00324:      * Returns the registered application object
00325:      *
00326:      * @return object Application
00327:      * @see Net_Growl_Application
00328:      */
00329:     public function getApplication()
00330:     {
00331:         return $this->_application;
00332:     }
00333:
00334:     /**
00335:      * Sends a application register to Growl
00336:      *
00337:      * @return Net_Growl_Response
00338:      * @throws Net_Growl_Exception if REGISTER failed
00339:      */
00340:     public function register()
00341:     {
00342:         return $this->sendRegister();
00343:     }
00344:
00345:     /**
00346:      * Sends a notification to Growl
00347:      *
00348:      * Growl notifications have a name, a title, a description and
00349:      * a few options, depending on the kind of display plugin you use.
00350:      * The bubble plugin is recommended, until there is a plugin more
00351:      * appropriate for these kind of notifications.
00352:      *
00353:      * The current options supported by most Growl plugins are:
00354:      * <pre>
00355:      * array('priority' => 0, 'sticky' => false)
00356:      * </pre>
00357:      * - sticky: whether the bubble stays on screen until the user clicks on it.
00358:      * - priority: a number from -2 (low) to 2 (high), default is 0 (normal).
00359:      *
00360:      * @param string $name        Notification name
00361:      * @param string $title       Notification title
00362:      * @param string $description (optional) Notification description
00363:      * @param string $options     (optional) few Notification options
00364:      *
00365:      * @return Net_Growl_Response | FALSE
00366:      * @throws Net_Growl_Exception if NOTIFY failed
00367:      */
00368:     public function notify($name, $title, $description = '', $options = array())
00369:     {
00370:         if ($this->_growlNotificationLimit > 0
00371:             && $this->growlNotificationCount >= $this->_growlNotificationLimit
00372:         ) {
00373:             // limit reached: no more notification displayed on user desktop
00374:             return false;
00375:         }
00376:
00377:         if (!$this->isRegistered) {
00378:             $this->sendRegister();
00379:         }
00380:         return $this->sendNotify($name, $title, $description, $options);
00381:     }
00382:
00383:     /**
00384:      * Send request to remote server
00385:      *
00386:      * @param string $method   Either REGISTER, NOTIFY
00387:      * @param mixed  $data     Data block to send
00388:      * @param bool   $callback (optional) Socket callback request
00389:      *
00390:      * @return Net_Growl_Response | TRUE
00391:      * @throws Net_Growl_Exception if remote server communication failure
00392:      */
00393:     protected function sendRequest($method, $data, $callback = false)
00394:     {
00395:         // @codeCoverageIgnoreStart
00396:         $addr = $this->options['protocol'] . '://' . $this->options['host'];
00397:
00398:         $this->debug(
00399:             $addr . ':' .
00400:             $this->options['port'] . ' ' .
00401:             $this->options['timeout']
00402:         );
00403:
00404:         // open connection
00405:         if (is_array($this->options['context'])
00406:             && function_exists('stream_context_create')
00407:         ) {
00408:             $context = stream_context_create($this->options['context']);
00409:
00410:             if (function_exists('stream_socket_client')) {
00411:                 $flags = STREAM_CLIENT_CONNECT;
00412:                 $addr  = $addr . ':' . $this->options['port'];
00413:                 $sh = @stream_socket_client(
00414:                     $addr, $errno, $errstr,
00415:                     $this->options['timeout'], $flags, $context
00416:                 );
00417:             } else {
00418:                 $sh = @fsockopen(
00419:                     $addr, $this->options['port'],
00420:                     $errno, $errstr, $$this->options['timeout'], $context
00421:                 );
00422:             }
00423:         } else {
00424:             $sh = @fsockopen(
00425:                 $addr, $this->options['port'],
00426:                 $errno, $errstr, $$this->options['timeout']
00427:             );
00428:         }
00429:
00430:         if ($sh === false) {
00431:             $this->debug($errstr, 'error');
00432:             $error = 'Could not connect to Growl Server.';
00433:             throw new Net_Growl_Exception($error);
00434:         }
00435:         stream_set_timeout($sh, $this->options['timeout'], 0);
00436:
00437:         $this->debug($data);
00438:         $res = fwrite($sh, $data, mb_strlen($data));
00439:
00440:         if ($res === false) {
00441:             $error = 'Could not send data to Growl Server.';
00442:             throw new Net_Growl_Exception($error);
00443:         }
00444:
00445:         switch ($this->options['protocol']) {
00446:         case 'tcp':
00447:             // read GNTP response
00448:             $line = $this->_readLine($sh);
00449:             $this->debug($line);
00450:             $response = new Net_Growl_Response($line);
00451:             $statusOK = ($response->getStatus() == 'OK');
00452:             while (mb_strlen($line) > 0) {
00453:                 $line = $this->_readLine($sh);
00454:                 $response->appendBody($line."\r\n");
00455:                 if (is_resource($this->_fp)) {
00456:                     $this->debug($line);
00457:                 }
00458:             }
00459:
00460:             if ($statusOK
00461:                 && $callback === true
00462:                 && $method == 'NOTIFY'
00463:             ) {
00464:                 // read GNTP socket Callback response
00465:                 $line = $this->_readLine($sh);
00466:                 $this->debug($line);
00467:                 if (preg_match('/^GNTP\/1.0 -(\w+).*$/', $line, $resp)) {
00468:                     $res = ($resp[1] == 'CALLBACK');
00469:                     if ($res) {
00470:                         while (mb_strlen($line) > 0) {
00471:                             $line = $this->_readLine($sh);
00472:                             $this->debug($line);
00473:                             $eon = true;
00474:
00475:                             $nid = preg_match(
00476:                                 '/^Notification-ID: (.*)$/',
00477:                                 $line, $resp
00478:                             );
00479:                             if ($nid) {
00480:                                 $eon = false;
00481:                             }
00482:
00483:                             $ncr = preg_match(
00484:                                 '/^Notification-Callback-Result: (.*)$/',
00485:                                 $line, $resp
00486:                             );
00487:                             if ($ncr) {
00488:                                 $this->growlNotificationCallback[] = $resp[1];
00489:                                 $eon = false;
00490:                             }
00491:
00492:                             $ncc = preg_match(
00493:                                 '/^Notification-Callback-Context: (.*)$/',
00494:                                 $line, $resp
00495:                             );
00496:                             if ($ncc) {
00497:                                 $this->growlNotificationCallback[] = $resp[1];
00498:                                 $eon = false;
00499:                             }
00500:
00501:                             $ncct = preg_match(
00502:                                 '/^Notification-Callback-Context-Type: (.*)$/',
00503:                                 $line, $resp
00504:                             );
00505:                             if ($ncct) {
00506:                                 $this->growlNotificationCallback[] = $resp[1];
00507:                                 $eon = false;
00508:                             }
00509:
00510:                             $nct = preg_match(
00511:                                 '/^Notification-Callback-Timestamp: (.*)$/',
00512:                                 $line, $resp
00513:                             );
00514:                             if ($nct) {
00515:                                 $this->growlNotificationCallback[] = $resp[1];
00516:                                 $eon = false;
00517:                             }
00518:
00519:                             if ($eon) {
00520:                                 break;
00521:                             }
00522:                         }
00523:                     }
00524:                 }
00525:
00526:                 if (is_resource($this->_fp)) {
00527:                     while (mb_strlen($line) > 0) {
00528:                         $line = $this->_readLine($sh);
00529:                         $this->debug($line);
00530:                     }
00531:                 }
00532:             }
00533:             break;
00534:         case 'udp':
00535:             $statusOK = $response = true;
00536:             break;
00537:         }
00538:
00539:         switch (strtoupper($method)) {
00540:         case 'REGISTER':
00541:             if ($statusOK) {
00542:                 $this->isRegistered = true;
00543:             }
00544:             break;
00545:         case 'NOTIFY':
00546:             if ($statusOK) {
00547:                 $this->growlNotificationCount++;
00548:             }
00549:             break;
00550:         }
00551:
00552:         // close connection
00553:         fclose($sh);
00554:
00555:         return $response;
00556:         // @codeCoverageIgnoreEnd
00557:     }
00558:
00559:     /**
00560:      * Returns Growl default icon logo binary data
00561:      * Decodes data encoded with MIME base64
00562:      *
00563:      * @param bool $return (optional) If used and set to FALSE,
00564:      *                     getDefaultGrowlIcon() will output the binary
00565:      *                     representation instead of return it
00566:      *
00567:      * @return string
00568:      */
00569:     public function getDefaultGrowlIcon($return = true)
00570:     {
00571:         $growl_logo
00572:             = 'iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAA'
00573:             . 'AARnQU1BAACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAA'
00574:             . 'OpgAABdwnLpRPAAAAAlwSFlzAAALEgAACxIB0t1+/AAACthJREFUaEPtmAlUlPUa'
00575:             . 'h1UUkX0RFBEQwaUCd1D2xWGbGWTYZd9lkFUEEQzFJTKyrFtamZZbmZaZpbaZaZqV'
00576:             . 'W4taaqam5kIqatpyQZ/7zpCde7rZ7Z60vOfwnfOe/8c5H9/8nnef6dCh/Wr3QLsH'
00577:             . '2j3waw9ER0whOmyC3jQRVcSqn+D/xksxyhmEB2hRhZQzJrRCbAKxkWVihaRqqu98'
00578:             . 'kOSYKUyZOI+spHqiQsvRRJYTp6oSkFQSQ4zJT0i4syEWPL6SK5cvcPTgl2x6eT0r'
00579:             . '5i1jfFYN8VElxI4eSF2GMYXJuXc2RFGGhoqcMJY9XM5nby7jw1fXUlv2AFkaP05t'
00580:             . '82R1o+OdDaAr2LjQwcSFDqJ+Qh4tVy5weO8R1i0fC9cS+P4LbxoK+93ZEAXpZWQn'
00581:             . 'JvDEo0tou85Da5WcWrgSzdE3BlCdl/T3QuTGKchL0PymiKzYMSSrPHl7/VZOn/iC'
00582:             . 'H67OEfGVYhXQkgHN4ax/zPWvA0iMWYBydBUKPy1B3kWEjFIQH2REaqgxKQojksMc'
00583:             . 'yYhJ/0VQVrQvyaE2TMhR8cJTobS2lIv4yT9DlML1XC7uGUyNtvj2Q0SH1xLql89o'
00584:             . 'Xy2B3oX4exXhMzwLpa8jSaOtxGyIDbIgyrcLaj87UtQRZKmdyVZak6M058h+yX1q'
00585:             . 'xSaJ6dLo5/Oiimdnjrm9AEkxj6DwySfAcxzKkGJSYgqJDFDiM1RN0PABxIdYkBBi'
00586:             . 'g9rXklBPC0KGmaD2MSJLaUO2qjtPT3cXwRPEqn8WfwNAonEtk40LA28vQFRoFQXp'
00587:             . 'M3lu0Wt8dfAoLT9c4uAnm3lr7XIq8pUoRxoR629NRWIvVs0ZwuQcV5TeFmQqbckR'
00588:             . 'gIUCcPzjdL7Znc3xHWnseyuJs3vzpA6kFshj1yrv2wswuXweTSeP0Xz2AAc+eZdN'
00589:             . 'r6/k6KG9+r5yX00aqQoL1sz34dzeMSIqlxOfxktt2JAe0Z34YGs8+poQ7u1GjGIw'
00590:             . 'kb79ifAdSEGCDx+skrS6lMHBV28zgK6nL130HtrMyYxVBZOuHkLF+Eo2rF1DpsqJ'
00591:             . 'HRvCBKVYrEQKU86ftEwe50p8oBUqH0tMu3XC1NgQe1sLbCxNcOhhhcddfZmYHUzT'
00592:             . 'Jg3nN3rd3gj81haZnjCOSG8HGie6tfV1ytoALmu53lzIlheCJa0s9RFw7tmVzgYd'
00593:             . 'MDMWEIGxMuuCq3NPEtXebFukpGXPqL8eIC+lgqTATmxbOYrWM2nsWafmxTkBzC71'
00594:             . 'ojLTiyfv9SU13I64YCsiRllgZ9UZI8OOmHTriJV5V9xcHAj3H8zWpVFwMpzVD//3'
00595:             . 'gZYS10hB9qJbBztujC07n/Vg3gQ3nOy6YWFiKCliiqO9LXe5OeDtYUPQUDOSFDYC'
00596:             . 'YYmDrSGWpgZYWxjRy86StEg3Dr/kC58Hcm5nIDMqZ9xUXNt6XoIqWEtOyvQ/B1GY'
00597:             . 'UUlOwlgyIu1ZUOUk26U95sYd6dqlA9bmhpIedtzd34l+fXrg4WpCpEQgcbQ10dKp'
00598:             . 'fDzMpMCteKTEmc+WDObaTgE4nybpV86XH6koTosgN2XyLwILUsZRmpPPh9s+Y9+n'
00599:             . 'h3nq0ecoyCijsvTp/w0iLXm5rAVhMnGNRUAXxvgaydCylF5vy9xiJ33B6lLEsLNA'
00600:             . 'WBjT37W3mBMDXaz1z8UH2+gjERtkzdwSJ9jtDV9Hyk6U01Y/16R+KGXbmiDCRxig'
00601:             . '8Tckxt+AREUPUqL82Lp5i77rXTnzNdMm1lGunfnHAKIjphIwMp/hHpkytLzkC4kZ'
00602:             . 'Y/wsCR9pjmKEOVHSZdLCuvNYiSNztA4ovcxwdzJkoLMFI9z7oI0dwIbGu9Fq7IgR'
00603:             . 'yJhAax4tFYCvpHO1jmvrXq3jf7H5s0YwepiZ/lm1nxXKgAHS9dyp1caw8tnnObH7'
00604:             . 'PZ6eM5eKotm/D5CVuojAkbkybTNR+OcToywkITKMsQoZWMmOVKW7MHfiPezZEMr+'
00605:             . '9yP57J3RcErDlS/U7HvNn/cWDmHbYj8ubFfDwXCW17miCRCAAGupGWc4Kt5vzRfP'
00606:             . 'FwqEzkrYujZCP8l1Ez1eij8m0J7CjHSqCpJoKFVQXVzOrCkzmT2tAW1K4s0B8jIX'
00607:             . '4zssg/SEKaxY+ioH9u3j8sVv2bj+eR6vu4cfm5JpbUqRlpmuX8i4JlP1O7FmMb0Y'
00608:             . 'sWsi7qds+CELzsVx6GVP8qNs9Sk0NbMXx9eNhG81cFXe810qO14PJ0Nlr5/gCSHW'
00609:             . 'AmrBrHJ3Vi6toFRbzcTCIiaXTWLyhHoeqq9iyUz3mwMEeOZSWtBI8/lL+ry7cX15'
00610:             . 'YB/vrg6WP3WhF7HfZ9O8J5Fty9S8MjeCzQvVfL0lgX+eFLB/yjOtBTKZBeRSMj9+'
00611:             . 'rGBJjZu+XlZNd2PnU3dz/o0RHN/gxTO1bgQOMsXXw1SfYmOlVmICLJk3xYPrPxSw'
00612:             . 'euUkyfkGxudMo2H6VA7tSObkluG/DVA2fhUN9fO51NxM0+njbN+0kU3rN7Br+0ds'
00613:             . 'fec9/jHVh8tNcXqIA2/HUZTkhcJ3EH6eAxnu7kzAMCcaS4ZzdX+SQEiBfi8RaJb7'
00614:             . 'I0qOvuhF09s+tO4KYOcCd2pS7PF3N8XcpBPdDDvJaYBLr676ljtW1vDx0T05/mmM'
00615:             . 'fNYsjhxaz8a1RTR/FQ0XfNm8LOzmEdi+9QtefnEXs+pXU1u5/JcH6+99lZJsLfcV'
00616:             . '2rDlmSFkqvvT16kXjr1ssLMxpYdYzx42DBrQmzWNo+BELFxMhCY5T0gt7FcIiIrt'
00617:             . 'S4PI1dwlbdYOU5Nuuvdj0KnNOorZWnbWR6Iktgcnd4RIOqZKuorwpgA448+P+wez'
00618:             . 'sHHWH+tCN/sxSqtxYZBLB1kNDPTrgbmxAWYmXelhbSKpYMU66Twck0I9J+JPyYd/'
00619:             . 'IwDHVXyw2J/oEHdGDnPX70M2VmYYyJqhWzV0prvXAQ3tb0KVROijxe5c3D2S64d8'
00620:             . '5P8DaDngxevz/f6c+BtQ1flqNH6GBMi0VUs7nZHrwOr73Tj9+nA4JF3phFK8JuJP'
00621:             . 'y7pwRs31g5FMyx2Iax8HBt/dl7v6OeoBboi/cXaSKOj2Jl0aVSTZU5NqzxypnTcf'
00622:             . '6c+Kul63RvwNiLqieAo1pkwSb13cNEK8Hii7TUSb6NPi9TM/n01RXP1E2nCoE7bd'
00623:             . 'LenTuzu9e1phZmr0HwC6VNJFwXOgCTXpvbi/0Ill01xEvAMPT516awF0INV5YRRp'
00624:             . 'TDj22jDxeDicVYl4MfE6p3T3EomzEoGTKp6sdqenrTnWslZbmHbFsEsnPUCXf0sf'
00625:             . 'nfhBfbux9oH+XN4jE/uID8c2eLDowTm3XvyNSFSke/JYmTWH1g7j+ufSas9I/jfp'
00626:             . 'IET8KbnXnQJ2dXeIpEUfrGTd6GxgQKeOHfSm83qXzh1lKTQkLbQ72+brashfJvYI'
00627:             . 'Plr2Oz3/Vv5iXFOYwISE7iyaZM/hNUNp3Svp9KV0kYNSD8dkdTgSKoJC+W6rPw+O'
00628:             . 'd2Wwm7RQyXVr8870czSSCWzF4+XOnHxpCGwfxuFXBrBitur2ef1m8JPyIpmYaMW8'
00629:             . 'MjvelcI7snIIV94ZScv7Plz/QLbPXf60bPbmwyfv4alKF6ZmO3BvlgNPVDrzxoMu'
00630:             . 'vPmgAwtrh/71wn8NVFccR/lYJ2rTLHi02JrnptjzRmMfts/TTeJ+vD/PlXWznXly'
00631:             . 'oh0zcy2ZmWfPrCJvHpre8PeL/zVMQ0059WXJ1BVGUKcNpn58EDOKFbLvjGH2pBzm'
00632:             . 'zrj5l5lbmebt72r3QLsH2j3Q7oF2D9xxHvgXsaxDNYPEU7QAAAAASUVORK5CYII=';
00633:
00634:         $data = base64_decode($growl_logo);
00635:
00636:         if ($return === false) {
00637:             // @codeCoverageIgnoreStart
00638:             if (headers_sent()) {
00639:                 return;
00640:             }
00641:             header('content-type: image/png');
00642:             echo $data;
00643:             exit();
00644:             // @codeCoverageIgnoreEnd
00645:         } else {
00646:             return $data;
00647:         }
00648:     }
00649:
00650:     /**
00651:      * Logs GNTP IN/OUT messages
00652:      *
00653:      * @param string $message  String containing the message to log
00654:      * @param string $priority (optional) String containing a priority name
00655:      *
00656:      * @return void
00657:      */
00658:     protected function debug($message, $priority = 'debug')
00659:     {
00660:         if (is_resource($this->_fp)
00661:             && mb_strlen($message) > 0
00662:         ) {
00663:             fwrite(
00664:                 $this->_fp,
00665:                 date("Y-m-d H:i:s") . " [$priority] - " . $message . "\n"
00666:             );
00667:         }
00668:     }
00669:
00670:     /**
00671:      * Converts standard error into exception
00672:      *
00673:      * @param int    $errno   contains the level of the error raised
00674:      * @param string $errstr  contains the error message
00675:      * @param string $errfile contains the filename that the error was raised in
00676:      * @param int    $errline contains the line number the error was raised at
00677:      *
00678:      * @return void
00679:      * @throws ErrorException when a standard error occured with severity level
00680:      *                        we are asking for (uses error_reporting)
00681:      * @since 2.1.0
00682:      */
00683:     public function errorHandler($errno, $errstr, $errfile, $errline)
00684:     {
00685:         // Only catch errors we are asking for
00686:         if ((error_reporting() & $errno) == 0) {
00687:             return;
00688:         }
00689:         throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
00690:     }
00691:
00692:     /**
00693:      * Autoloader for PEAR compatible classes
00694:      *
00695:      * @param string $class Class name
00696:      *
00697:      * @return void
00698:      * @throws Net_Growl_Exception if class handler cannot be loaded
00699:      */
00700:     public static function autoload($class)
00701:     {
00702:         try {
00703:             $path = str_replace('_', '/', $class .'.php');
00704:             include_once $path;
00705:         }
00706:         catch (ErrorException $e) {
00707:             $message = 'Cannot load class "'.$class.'"';
00708:             throw new Net_Growl_Exception($message);
00709:         }
00710:     }
00711:
00712:     /**
00713:      * Read until either the end of the socket or a newline, whichever
00714:      * comes first. Strips the trailing newline from the returned data.
00715:      *
00716:      * @param mixed $fp a file pointer resource
00717:      *
00718:      * @return All available data up to a newline, without that
00719:      *         newline, or until the end of the socket,
00720:      * @throws Net_Growl_Exception if not connected
00721:      */
00722:     private function _readLine($fp)
00723:     {
00724:         // @codeCoverageIgnoreStart
00725:         if (!is_resource($fp)) {
00726:             throw new Net_Growl_Exception('not connected');
00727:         }
00728:
00729:         $line = '';
00730:         $timeout = time() + $this->options['timeout'];
00731:         while (!feof($fp) && (time() < $timeout)) {
00732:             $line .= @fgets($fp);
00733:             if (mb_substr($line, -1) == "\n" && mb_strlen($line) > 0) {
00734:                 break;
00735:             }
00736:         }
00737:         return rtrim($line, "\r\n");
00738:         // @codeCoverageIgnoreEnd
00739:     }
00740: }
00741: ?>