Alphred
  • Namespace
  • Class
  • Tree
  • Deprecated
  • Todo
  • Download

Namespaces

  • Alphred
  • None

Classes

  • Alphred
  1 <?php
  2 /**
  3  * Contains ScriptFilter and Result class for Alphred to work with script filters
  4  *
  5  * PHP version 5
  6  *
  7  * @package      Alphred
  8  * @copyright  Shawn Patrick Rice 2014
  9  * @license    http://opensource.org/licenses/MIT  MIT
 10  * @version    1.0.0
 11  * @author     Shawn Patrick Rice <rice@shawnrice.org>
 12  * @link       http://www.github.com/shawnrice/alphred
 13  * @link       http://shawnrice.github.io/alphred
 14  * @since      File available since Release 1.0.0
 15  *
 16  */
 17 
 18 namespace Alphred;
 19 
 20 /**
 21  * Creates Script Filter XML for Alfred
 22  *
 23  * Example usage:
 24  *
 25  * ````php
 26  * $script_filter = new Alphred\ScriptFilter( ['error_on_empty'] => true );
 27  * $script_filter->add_result( new Alphred\Result([
 28  *  'title' => 'This is a title',
 29  *  'subtitle' => 'This is a subtitle',
 30  *  'valid' => false
 31  * ]));
 32  * $script_filter->to_xml();
 33  * ````
 34  *
 35  * @uses Result           Result items are stored in the script filter
 36  * @since 1.0.0
 37  *
 38  */
 39 class ScriptFilter {
 40     /**
 41      * Constructs a script filter container
 42      *
 43      * @since 1.0.0
 44      *
 45      * @param array $options specify options:... see>>>?
 46      */
 47     public function __construct( $options = [] ) {
 48 
 49         $this->i18n = false;
 50         foreach ( ['localize', 'localise', 'i18n' ] as $localize ) :
 51             if ( isset( $options[ $localize ] ) && $options[ $localize ] ) {
 52                 $this->initializei18n();
 53                 break;
 54             }
 55         endforeach;
 56 
 57         // We'll just save all the options for later use if necessary
 58         $this->options = $options;
 59 
 60         // Create an array to hold the results
 61         $this->results = [];
 62 
 63         // Create the XML writer
 64         $this->xml = new \XMLWriter();
 65 
 66     }
 67 
 68     /**
 69      * Initializes a i18n Alphred object to use internally
 70      *
 71      * @since 1.0.0
 72      * @see \Alphred\i18n
 73      *
 74      */
 75     private function initializei18n() {
 76         if ( class_exists( '\Alphred\i18n' ) ) {
 77             $this->il18 = new i18n;
 78         } else {
 79             \Alphred\Log::console( 'Error: cannot find i18n class.', 0 );
 80         }
 81     }
 82 
 83     /**
 84      * Translates a string using the i18n class
 85      *
 86      * @since 1.0.0
 87      * @see \Alphred\i18n
 88      *
 89      * @param  string $string a string to translate
 90      * @return string         the string, translated if possible
 91      */
 92     private function translate( $string ) {
 93         // Check if the translation is turned on
 94         if ( ! $this->i18n ) {
 95             // No translation, so just return the string
 96             return $string;
 97         }
 98         // Try to return the translation
 99         return $this->i18n->translate( $string );
100     }
101 
102 
103     /**
104      * Adds a result into the script filter
105      *
106      * @since 1.0.0
107      * @see \Alphred\Result
108      *
109      * @param \Alphred\Result $result an Alphred\Result object
110      */
111     public function add_result( \Alphred\Result $result ) {
112         if ( ! ( is_object( $result ) && ( 'Alphred\Result' == get_class( $result ) ) ) ) {
113             // Double-check that the namespacing doesn't affect the return value of "get_class"
114             // raise an exception instead
115             return false;
116         }
117         array_push( $this->results, $result );
118     }
119 
120 
121     /**
122      * Returns an array of the results
123      *
124      * @since 1.0.0
125      *
126      * @return array an array of the current result items
127      */
128     public function get_results() {
129         return $this->results;
130     }
131 
132     /**
133      * Alias of to_xml()
134      *
135      * @since 1.0.0
136      * @see \Alphred\ScriptFilter::to_xml()
137      */
138     public function print_results() {
139         $this->to_xml();
140     }
141 
142     /**
143      * Outputs the script filter in Alfred XML
144      *
145      * @since 1.0.0
146      *
147      */
148     public function to_xml() {
149 
150         // If the user requested to have an item when the script filter was empty, then we'll
151         // supply a very generic one
152         if ( isset( $this->options['error_on_empty'] ) ) {
153             if ( 0 === count( $this->get_results() ) ) {
154                 // A generic "no results found" response
155                 $result = new Result( [
156                     'title'          => 'Error: No results found.',
157                     'icon'           => '/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/Unsupported.icns',
158                     'subtitle'     => 'Please try a different query.',
159                     'autocomplete' => '',
160                     'valid'        => false
161                 ]);
162                 $this->add_result( $result );
163             }
164         }
165 
166         // Begin the XML generation
167         $this->xml->openMemory();
168         $this->xml->setIndent( 4 );
169         $this->xml->startDocument( '1.0', 'UTF-8' );
170         $this->xml->startElement( 'items' );
171 
172         // Cycle through all results and generate the XML
173         foreach ( $this->results as $result ) :
174             $this->write_item( $result );
175         endforeach;
176         // End the xml document
177         $this->xml->endDocument();
178         // Print out the XML
179         print $this->xml->outputMemory();
180     }
181 
182     /**
183      * Writes out the Alfred XML
184      *
185      * @since 1.0.0
186      *
187      * @param object $item An \Alphred\Result object
188      */
189     private function write_item( $item ) {
190         // The information we need is stored in the sub variable, so let's just get that
191         $item = $item->data;
192         // These go in the 'item' part as an attribute
193         $attributes = [ 'uid', 'arg', 'autocomplete' ];
194         // This is either true or false
195         $bool = [ 'valid' ];
196 
197         // Start the element
198         $this->xml->startElement( 'item' );
199 
200         // Cycle through all the attributes. If they are set, then write them out
201         foreach ( $attributes as $v ) :
202             if ( isset( $item[ $v ] ) ) {
203                 $this->xml->writeAttribute( $v, $item[ $v ] );
204             }
205         endforeach;
206 
207         // Translate 'valid' from a boolean to the 'yes' or 'no' value that Alfred wants to see
208         if ( isset( $item['valid'] ) && in_array( strtolower( $item['valid'] ), ['yes', 'no', true, false] ) ) {
209             if ( 'no' == strtolower( $item['valid'] ) ) {
210                 $item['valid'] = false;
211             }
212             $valid = $item['valid'] ? 'yes' : 'no';
213             $this->xml->writeAttribute( 'valid', $valid );
214         }
215 
216         // Cycle through the $item array and set everything. The keys are the, well, keys, and
217         // the values are the values. ( $array => xml )
218         foreach ( $item as $k => $v ) :
219             // Make suure that the bit of data is not in either the $attributes or $bool array
220             if ( ! in_array( $k, array_merge( $attributes, $bool ) ) ) {
221                 // Check to see, first, if we need to add attributes by parsing the key
222                 if ( false !== strpos( $k, '_' ) && 0 === strpos( $k, 'subtitle' ) ) {
223                     $this->xml->startElement( substr( $k, 0, strpos( $k, '_' ) ) );
224                     $this->xml->writeAttribute( 'mod', substr( $k, strpos( $k, '_' ) + 1 ) );
225                 } else if ( false !== strpos( $k, '_' ) ) {
226                     // Add in checks for icon filetype
227                     // These are the general sub-items
228                     $this->xml->startElement( substr( $k, 0, strpos( $k, '_' ) ) );
229                     $this->xml->writeAttribute( 'type', substr( $k, strpos( $k, '_' ) + 1 ) );
230                 } else {
231                     // There are no attributes, so just start the sub-element
232                     $this->xml->startElement( $k );
233                 }
234                 // Put in the text (value), and translate it for us if we're using the i18n class
235                 $this->xml->text( $this->translate( $v ) );
236                 // Close the sub-element
237                 $this->xml->endElement();
238             }
239         endforeach;
240         // End the item
241         $this->xml->endElement();
242     }
243 
244 }
245 
246 /**
247  * Result class
248  *
249  * Class object represents an item in the script filter array. The internals of the
250  * class check for validity so that only correct methods can be set.
251  *
252  * @since 1.0.0
253  * @see ScriptFilter::add_result() These items are part of the ScriptFilter
254  *
255  * @method void set_arg(            string $arg          ) the argument to pass
256  * @method void set_autocomplete(   string $autocomplete ) autocomplete text
257  * @method void set_icon(           string $icon         ) path to icon
258  * @method void set_icon_fileicon(  string $fileicon     ) path to application
259  * @method void set_icon_filetype(  string $filetype     ) filetype for icon
260  * @method void set_subtitle(       string $subtitle     ) subtitle text
261  * @method void set_subtitle_alt(   string $subtitle     ) alt subtitle text
262  * @method void set_subtitle_cmd(   string $subtitle     ) cmd subtitle text
263  * @method void set_subtitle_ctrl(  string $subtitle     ) ctrl subtitle text
264  * @method void set_subtitle_fn(    string $subtitle     ) fn subtitle text
265  * @method void set_subtitle_shift( string $subtitle     ) shift subtitle text
266  * @method void set_text_copy(      string $text         ) text to pass when copying
267  * @method void set_text_largetype( string $text         ) text to pass to large type
268  * @method void set_title(          string $title        ) title of result
269  * @method void set_uid(            string $uid          ) uid for result
270  */
271 class Result {
272 
273     /**
274      * Possible string methods for a Result
275      *
276      * @var array
277      */
278     private static $string_methods = [
279         'title',
280         'icon',
281         'icon_filetype',
282         'icon_fileicon',
283         'subtitle',
284         'subtitle_shift',
285         'subtitle_fn',
286         'subtitle_ctrl',
287         'subtitle_alt',
288         'subtitle_cmd',
289         'uid',
290         'arg',
291         'text_copy',
292         'text_largetype',
293         'autocomplete'
294     ];
295 
296     /**
297      * Possible boolean methods for a Result
298      * @var array
299      */
300     private static $bool_methods = [ 'valid' ];
301 
302 
303     /**
304      * Creates a Result object
305      *
306      * @param array|string $args the title if string; a list of arguments if an array
307      */
308     public function __construct( $args ) {
309 
310         // Create the data storage variable
311         $this->data = [];
312 
313         // If it is a string, then it's the title; if it's an array, then it's multiple values
314         if ( is_string( $args ) ) {
315             // Set the title
316             $this->set_title( $args );
317         } else if ( is_array( $args ) ) {
318             // It's an array, so, cycle through each value and set it
319             foreach ( $args as $key => $value ) :
320                 $this->set( [ $key => $value ] );
321             endforeach;
322         }
323 
324     }
325 
326     /**
327      * Sets a multiple values of a result object
328      *
329      * @throws \Alphred\InvalidScriptFilterArgument When trying to set an invalid script filter field
330      *
331      * @param array $options an array of possible options
332      */
333     public function set( $options ) {
334         // Options must be an array of 'key' => 'value', like: 'title' => 'This is a title'
335         if ( ! is_array( $options ) ) {
336             return false;
337         }
338         // Cycle through the options and see if they are in either $string_methods or $bool_methods;
339         // if so, call them via the magic __call(); otherwise, thrown an exception.
340         foreach ( $options as $option => $value ) :
341             $method = "set_{$option}";
342           if ( in_array( $option, self::$string_methods ) || in_array( $option, self::$bool_methods ) ) {
343             $this->$method( $value );
344           } else {
345             // Not valid. Throw an exception.
346                 throw new InvalidScriptFilterArgument( "Error: `{$method}` is not valid.", 3 );
347             }
348         endforeach;
349     }
350 
351     /**
352      * Magic method to set everything necessary
353      *
354      * @todo Convert the 'false' returns to thrown Exceptions
355      * @throws \Alphred\TooManyArguments when trying to use multiple values
356      * @throws \Alphred\InvalidXMLProperty when trying to set an invalid XML property
357      *
358      * @param  string $called    method called
359      * @param  array $arguments  array of arguments
360      * @return bool
361      */
362     public function __call( $called, $arguments ) {
363         // Make sure that the method is supposed to exist
364         if ( 0 !== strpos( $called, 'set_' ) ) {
365             // We should raise an exception here instead.
366             return false;
367         }
368         // There should only be one argument in the arguments array
369         if ( 1 === count( $arguments ) ) {
370             // Remove the "set_" part of the 'method'
371             $method = str_replace( 'set_', '', $called );
372             // If the value is a bool, then check to make sure it's supposed to be a bool
373             if ( is_bool( $arguments[0] ) && ( in_array( $method, self::$bool_methods ) ) ) {
374                 // Set the data
375                 $this->data[ $method ] = $arguments[0];
376                 return true;
377             } else if ( in_array( $method, self::$string_methods ) ) {
378                 // Set the data
379                 $this->data[ $method ] = $arguments[0];
380                 return true;
381             } else {
382                 if ( in_array( $method, self::$bool_methods ) ) {
383                     throw new ShouldBeBool( "`{$method}` should be passed as bool not string" );
384                 } else {
385                     throw new InvalidXMLProperty( "`{$method}` is not a valid property for a script filter.", 3 );
386                 }
387             }
388         } else {
389             throw new TooManyArguments( "Expecting a single argument when trying to `{$called}` but got multiple.", 3 );
390         }
391     }
392 }
Alphred API documentation generated by ApiGen