1: <?php
2:
3: /**
4: * PHP API for interacting with OSX dialog spawner Terminal Notifier.
5: *
6: * Terminal Notifier is a resource which allows the user to spawn MacOSX notifications
7: * from the command-line. Notifications return nothing.
8: *
9: * Official Documentation {@link https://github.com/alloy/terminal-notifier}
10: * LICENSE: GPLv3 {@link https://www.gnu.org/licenses/gpl-3.0.txt}
11: *
12: * -> Usage
13: * ===========================================================================
14: *
15: * To include this api in your PHP scripts, copy this ``terminalnotifier.php``
16: * to a viable place for you to include.
17: *
18: * Build the Terminal Notifier client:
19: *
20: * include('terminalnotifier');
21: * $client = new TerminalNotifier('path to terminal-notifier.app or exec', $debug=Boolean)
22: *
23: * Now that you have access to the client, you can call specific dialogs:
24: *
25: * $my_notification = $client.notify([
26: * 'title'=>'My Notification',
27: * 'subtitle'=>'Hello, World!',
28: * 'message'=>'Have a nice day!',
29: * 'sender'=>'com.apple.Finder']);
30: *
31: * **NOTE**: The included `debug` parameter is very useful for finding out why
32: * your specified parameters are not being shown, or why your parameters are not
33: * passing as valid parameters, and thus the dialog is not being spawned.
34: *
35: * -> Revisions
36: * ============================================================================
37: * 1.0, 07-28-14: Initial release
38: *
39: * @copyright Ritashugisha 2014
40: * @license https://www.gnu.org/licenses/gpl-3.0.txt GPL v3
41: * @version 1.0
42: */
43:
44: $AUTHOR = 'Ritashugisha <ritashugisha@gmail.com>';
45: $DATE = '07-28-14';
46: $VERSION = 1.0;
47:
48:
49: /**
50: * Main class used for interaction with Terminal Notifier.
51: *
52: * Public class used to initialize the Terminal Notifier interaction client.
53: * Client inintialization is built by:
54: *
55: * $client = new TerminalNotifier('path to terminal-notifier.app or exec', $debug=bool)
56: *
57: * Initializes valid and required options.
58: */
59: class TerminalNotifier {
60:
61: /**
62: * Reference to Terminal Notifier exec
63: *
64: * @access private
65: * @var string
66: */
67: private $notifier;
68:
69: /**
70: * Toggle for log output
71: *
72: * @access private
73: * @var boolean
74: */
75: private $debug;
76:
77: /**
78: * Associative array of valid notification options
79: *
80: * @access private
81: * @var array
82: */
83: private $valid_options;
84:
85: /**
86: * Array of required notification options
87: *
88: * @access private
89: * @var array
90: */
91: private $required_options;
92:
93: /**
94: * This function prints debug logs to the console.
95: *
96: * @param string $level Logging level (adapted from Python's logging module)
97: * @param string $funct Calling function's name
98: * @param integer $lineno Calling functions line number
99: * @param string $message Desired message
100: */
101: function log( $level, $funct, $lineno, $message ) {
102: if ( $this->debug ) {
103: echo sprintf( "[%s] [%s:%d] [%s] %s\xA", date('Y-m-d h:i:s'), basename(__FILE__), $lineno, strtoupper( $level ), $message );
104: }
105: }
106:
107: /**
108: * TerminalNotifier class constructor.
109: *
110: * @access public
111: * @param string $notifier Path to either terminal-notifier.app or exec
112: * @param boolean $debug True if logging is enabled
113: */
114: public function __construct( $notifier, $debug=False ) {
115: $this->notifier = $notifier;
116: $this->debug = $debug;
117: date_default_timezone_set('UTC');
118: if ( file_exists( $this->notifier ) ) {
119: if ( 'app' === strtolower( pathinfo( $notifier, PATHINFO_EXTENSION ) ) ) {
120: $this->notifier = '/' . join( '/', array( trim( $notifier, '/' ),
121: trim( '/Contents/MacOS/terminal-notifier', '/' ) ) );
122: $valid_notifier = file_exists( $this->notifier );
123: }
124: else {
125: $valid_notifier = ( strtolower( basename( $this->notifier ) ) === 'terminal-notifier' );
126: }
127: }
128: else {
129: $valid_notifier = False;
130: }
131: if ( ! $valid_notifier ) {
132: $this->log( 'critical', __FUNCTION__, __LINE__,
133: sprintf( 'invalid path to terminal-notifier (%s)', $this->notifier ) );
134: die();
135: }
136: $this->valid_options = [
137: 'message' => ['string'],
138: 'title' => ['string'],
139: 'subtitle' => ['string'],
140: 'sound' => ['string'],
141: 'group' => ['string'],
142: 'remove' => ['string'],
143: 'activate' => ['string'],
144: 'sender' => ['string'],
145: 'appIcon' => ['string'],
146: 'contentImage' => ['string'],
147: 'open' => ['string'],
148: 'execute' => ['string']
149: ];
150: $this->required_options = ['message'];
151: }
152:
153: /**
154: * Run a process on the host system.
155: *
156: * @param string $process Process to be run
157: * @return string Output of process
158: */
159: public function _run_subprocess( $process ) {
160: // Preferrably, we should send a string rather than a list in PHP
161: if ( gettype( $process ) === 'array' ) {
162: $process = join( ' ', $process );
163: }
164: return shell_exec( $process );
165: }
166:
167: /**
168: * Validate and clean up passed notification options.
169: *
170: * @param array $passed Associative array of passed dialog arguemnts
171: */
172: public function _valid_options( $passed ) {
173: $_is_valid = True;
174:
175: // First we validate that all $passed options are valid options and \
176: // are the coresponding valid type
177: foreach ( array_keys( $passed ) as $passed_key ) {
178: if ( in_array( $passed_key, array_keys( $this->valid_options ) ) ) {
179: if ( ! in_array( gettype( $passed[$passed_key] ), $this->valid_options[$passed_key] ) ) {
180: $this->log( 'warning', __FUNCTION__, __LINE__,
181: sprintf( 'removing (%s) invalid type, expected (%s), got (%s)',
182: $passed_key,
183: implode( ' or ', array_values( $this->valid_options[$passed_key] ) ),
184: gettype( $passed[$passed_key] ) ) );
185: unset( $passed[$passed_key] );
186: }
187: }
188: else {
189: $this->log( 'warning', __FUNCTION__, __LINE__,
190: sprintf( 'removing (%s) invalid parameter, available are (%s)',
191: $passed_key,
192: implode( ', ', array_keys( $this->valid_options ) ) ) );
193: unset( $passed[$passed_key] );
194: }
195: }
196:
197: // Next we can check that the $passed options contain the required \
198: // options
199: foreach ( $this->required_options as $required_key ) {
200: if ( ! in_array( $required_key, array_keys( $passed ) ) ) {
201: $this->log( 'error', __FUNCTION__, __LINE__,
202: sprintf( 'missing required parameter (%s)', $required_key ) );
203: $_is_valid = False;
204: }
205: }
206:
207: // If the remove option is given, then we sould remove all other \
208: // options in order to allow room for notification removal to occur.
209: if ( in_array( 'remove', array_keys( $passed ) ) ) {
210: $_is_valid = True;
211: foreach ( array_keys( $passed ) as $k ) {
212: if ( substr( $k, 0, 6 ) !== 'remove' ) {
213: unset( $passed[$k] );
214: }
215: }
216: }
217:
218: // PHP is stupid, that's why we have to remember and return the formated passed
219: // arguments back to our notification
220: return [$_is_valid, $passed];
221: }
222:
223: /**
224: * Display the passed notification after some crutial formatting.
225: *
226: * @param array $passed Associative array of passed dialog options
227: */
228: public function _display( $passed ) {
229: $process = ["'$this->notifier'"];
230: foreach ( $passed as $k => $v ) {
231: array_push( $process, sprintf( '-%s', $k ) );
232: // It is important we escape the first character of every value
233: // passed. This is a known bug in TN.
234: array_push( $process, sprintf( '"\\%s"', $v ) );
235: }
236: try {
237: $this->log('info', __FUNCTION__, __LINE__, implode( ' ', $process ) );
238: $this->_run_subprocess( implode( ' ', $process ) );
239: } catch (Exception $e) {
240: $this->log('critical', __FUNCTION__, __LINE__, $e );
241: }
242: }
243:
244: /**
245: * Spawn a notification with passed arguments
246: *
247: * @param array $_passed Associative array of notification arguments
248: */
249: public function notify( array $_passed ) {
250: $_valid = $this->_valid_options( $_passed );
251: if ( $_valid[0] ) {
252: $this->_display( $_valid[1] );
253: }
254: }
255: }
256: