Overview

Packages

  • Horde
    • Block
  • Klutz
  • None

Classes

  • Klutz
  • Klutz_Comic
  • Klutz_Comic_Bysize
  • Klutz_Comic_Direct
  • Klutz_Comic_Search
  • Klutz_Driver
  • Klutz_Driver_File
  • Klutz_Driver_Sql
  • Klutz_Image
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * Klutz Comic Class.
  4:  *
  5:  * @author Marcus I. Ryan <marcus@riboflavin.net>
  6:  * @package Klutz
  7:  */
  8: class Klutz_Comic
  9: {
 10:     /**
 11:      * The title of the comics (Dilbert, The 5th Wave, etc.)
 12:      *
 13:      * @var string
 14:      */
 15:     var $name = null;
 16: 
 17:     /**
 18:      * The author or authors of the comic (the byline)
 19:      *
 20:      * @var string
 21:      */
 22:     var $author = null;
 23: 
 24:     /**
 25:      * The URL for the official homepage (not necessarily where we
 26:      * get the comic from.
 27:      *
 28:      * @var string
 29:      */
 30:     var $homepage = null;
 31: 
 32:     /**
 33:      * Days (lowercase, three-letter english abbreviation) that this comic is
 34:      * is available.
 35:      *
 36:      * @var array
 37:      */
 38:     var $days = array('mon','tue','wed','thu','fri','sat','sun');
 39: 
 40:     /**
 41:      * Some comment to display for this comic
 42:      *
 43:      * @var string
 44:      */
 45:     var $comment = null;
 46: 
 47:     /**
 48:      * Days behind the current date this comic is published
 49:      *
 50:      * @var string
 51:      */
 52:     var $offset = 0;
 53: 
 54:     /**
 55:      * Are past episodes available?  Some comics are difficult or
 56:      * impossible to retrieve other than the day it's published.
 57:      *
 58:      * @var boolean
 59:      */
 60:     var $nohistory = false;
 61: 
 62:     /*
 63:      * Parameters specific to fetching or otherwise processing the comic
 64:      */
 65: 
 66:     /**
 67:      * Web browser object used to fetch pages
 68:      *
 69:      * @var HTTP_Request
 70:      */
 71:     var $http = null;
 72: 
 73:     /**
 74:      * The first url we need to hit to get the comic we want.
 75:      *
 76:      * @var string
 77:      */
 78:     var $url;
 79: 
 80:     /**
 81:      * Headers we need to pass/override to be able to get the comic.
 82:      * These are passed to HTTP_Request.
 83:      */
 84: 
 85:     /**
 86:      * The referral URL to use when fetching the comic.
 87:      *
 88:      * @var string
 89:      */
 90:     var $referer = null;
 91: 
 92:     /**
 93:      * The user-agent to use when fetching the comic.
 94:      *
 95:      * @var string
 96:      */
 97:     var $agent = null;
 98: 
 99:     /**
100:      * The username to use when fetching the comic.
101:      *
102:      * @var string
103:      */
104:     var $user = null;
105: 
106:     /**
107:      * The password to use when fetching the comic.
108:      *
109:      * @var string
110:      */
111:     var $pass = null;
112: 
113:     /**
114:      * Cookies to set when fetching the comic.
115:      *
116:      * @var array
117:      */
118:     var $cookies = array();
119: 
120:     /**
121:      * Headers to set when fetching the comic.
122:      *
123:      * @var array
124:      */
125:     var $headers = array();
126: 
127:     /**
128:      * An array of the fields we need to do substitution on.
129:      *
130:      * @var array
131:      */
132:     var $subs = null;
133: 
134:     //
135:     // used for the {i} construct (for sites that id comics by "instance")
136:     //
137: 
138:     /**
139:      * Method for counting instances (when using the 'i' construct in
140:      * substitutions.
141:      *
142:      * @var string
143:      */
144:     var $itype = null;
145: 
146:     /**
147:      * Format string for the instance construct (printf string)
148:      *
149:      * @var string
150:      */
151:     var $iformat = '%d';
152: 
153:     /**
154:      * The number of the "first" instance of the comic (the reference
155:      * number) when using the reference-based instance type
156:      *
157:      * @var integer
158:      */
159:     var $icount = 0;
160: 
161:     /**
162:      * The date for which the reference is icount.
163:      *
164:      * @var date
165:      */
166:     var $idate = null;
167: 
168:     /**
169:      * The day the "week" starts for instance type weekly.
170:      * Abbreviated day name in english, lowercase.
171:      *
172:      * @var string
173:      */
174:     var $isow = 'sun';
175: 
176:     /**
177:      * The array of overrides by weekday.  If sun_url exists, then
178:      * when trying to fetch the sunday edition of this comic, it will
179:      * fetch it from the specified url instead of $url.
180:      *
181:      * @var array
182:      */
183:     var $override = array();
184: 
185:     /**
186:      * Loads the $comics[$comic] array into this object
187:      *
188:      * @param string $comic  The comic to create this object from
189:      */
190:     function Klutz_Comic(&$comic)
191:     {
192:         // what variables should we try to set directly from the comic array?
193:         $vars = array('name', 'author', 'homepage', 'comment', 'offset',
194:                       'url', 'itype', 'iformat', 'icount', 'idate', 'isow',
195:                       'referer', 'agent', 'user', 'pass', 'nohistory');
196: 
197:         // set the variables for this object that we've been passed
198:         foreach ($vars as $field) {
199:             if (!empty($comic[$field]) && !is_array($comic[$field])) {
200:                 $this->$field = $comic[$field];
201:                 unset($comic[$field]);
202:             }
203:         }
204: 
205:         if (!is_null($this->idate) && !is_numeric($this->idate)) {
206:             $this->idate = strtotime($this->idate);
207:         }
208: 
209:         // What arrays should we try to set from the comic array, and
210:         // do we want to perform a function?
211:         $arrays = array('days' => 'strtolower', 'headers' => null,
212:                         'subs' => 'strtolower', 'cookies' => null);
213: 
214:         // set the arrays - make sure each is an array & values lowercased!
215:         foreach ($arrays as $field => $function) {
216:             if (isset($comic[$field]) && is_array($comic[$field])) {
217:                 if (is_null($function)) {
218:                     $this->$field = $comic[$field];
219:                 } else {
220:                     $this->$field = array_map($function, $comic[$field]);
221:                 }
222:                 unset($comic[$field]);
223:             }
224:         }
225: 
226:         // Set any override strings in $this->override[]. Capitalize
227:         // and shorten the day keys to match date('D').
228:         if (isset($comic['override']) && is_array($comic['override']) && count($comic['override'])) {
229:             foreach ($comic['override'] as $dow => $value) {
230:                 if (strlen($dow) >= 3) {
231:                     $this->override[ucfirst(substr($dow,0,3))] = $value;
232:                 }
233:             }
234: 
235:         }
236: 
237:         // Anything left should be specific to the fetch driver.
238:         // Let the derivative class handle any extra parsing
239:     }
240: 
241:     /**
242:      * Create an HTTP_Request object and set all parameters necessary to
243:      * perform fetches for this comic.
244:      *
245:      * @param timestamp $date  Date of the comic to retrieve (default today)
246:      */
247:     function _initHTTP($date, $url)
248:     {
249:         if (is_null($this->http)) {
250:             $options = array();
251:             if (isset($GLOBALS['conf']['http']['proxy']) && !empty($GLOBALS['conf']['http']['proxy']['proxy_host'])) {
252:                 $options = array_merge($options, $GLOBALS['conf']['http']['proxy']);
253:             }
254: 
255:             require_once 'HTTP/Request.php';
256:             $this->http = new HTTP_Request($url, $options);
257: 
258:             $v = $this->getOverride("referer", $date);
259:             if (!is_null($v)) {
260:                 $this->http->addHeader('Referer', $v);
261:             }
262: 
263:             $v = $this->getOverride("agent");
264:             if (!is_null($v)) {
265:                 $this->http->addHeader('User-Agent', $v);
266:             }
267: 
268:             $user = $this->getOverride("user", $date);
269:             $pass = $this->getOverride("pass", $date);
270:             if (!is_null($user) and !is_null($pass)) {
271:                 $this->http->setBasicAuth($user, $pass);
272:             }
273: 
274:             foreach ($this->getOverride('cookies', $date) as $name => $value) {
275:                 $this->http->addCookie($name, $value);
276:             }
277:             foreach ($this->getOverride('headers', $date) as $name => $value) {
278:                 $this->addHeader($name, $value);
279:             }
280:         }
281:     }
282: 
283:     /**
284:      * Turn the search strings from the configuration file into
285:      * preg_match-formatted strings
286:      *
287:      * @param array $comic  An array containing the content portion of Perl
288:      *                      regular expressions
289:      *
290:      * @return array  Search strings properly formatted to be used with
291:      *                preg_match
292:      *
293:      * @access private
294:      */
295:     function _prepareSearch($search)
296:     {
297:         foreach (array_keys($search) as $i) {
298:             if (is_array($search[$i])) {
299:                 $search[$i] = $this->_prepareSearch($search[$i]);
300:             } elseif (substr($search[$i], 0, 1) != '|') {
301:                 $search[$i] = '|' . $search[$i] . '|i';
302:             }
303:         }
304:         return $search;
305:     }
306: 
307:     /**
308:      * Check for "override" settings - settings that override other
309:      * settings depending on the day on which the comic appears
310:      *
311:      * @param string $setting    The name of the setting to override
312:      * @param timestamp $date    The date to check for overrides
313:      * @param string $array_map  Filter to be used with array_map
314:      *
315:      * @return mixed  If the setting is an array, returns the setting passed
316:      *                through array_map if array_map was passed.  Otherwise,
317:      *                returns the value of the setting, overridden if an
318:      *                override is present
319:      */
320:     function getOverride($setting, $date = null, $array_map = null)
321:     {
322:         if (is_null($date)) {
323:             $date = mktime(0, 0, 0);
324:         }
325: 
326:         $day = date('D', $date);
327: 
328:         if (isset($this->override[$day][$setting])) {
329:             if ((isset($this->$setting) && is_array($this->$setting)) ||
330:                 !is_null($array_map)) {
331:                 if (is_array($this->$setting)) {
332:                     return array_map($array_map,
333:                                      array($this->override[$day][$setting]));
334:                 } else {
335:                     return array_map($array_map, $this->override[$day][$setting]);
336:                 }
337:             } else {
338:                 return $this->override[$day][$setting];
339:             }
340:         } else {
341:             return $this->$setting;
342:         }
343:     }
344: 
345:     /**
346:      * Process known substitutions in a string.  Currently known options:<br />
347:      * o {dow(int day, string format)} day is numeric day of the week, format
348:      *   format is an strftime string (e.g. '%Y%m%d'), replaced with the
349:      *   formatted date for the requested day of the week
350:      * o {i} replaced with the instance of this comic as determined by
351:      *   the various instance configuration options<br />
352:      * o {format} format is an strftime string, replaced with todays date
353:      *   formatted according to the format string<br />
354:      * o {lc(string)} replaces string with string lowercased<br />
355:      * o {uc(string)} replaces string with string uppercased<br />
356:      * o {t(string)} removes extra space surrounding string<br />
357:      * o {tl0(string)} removes leading zeroes from string
358:      *
359:      * @param string $string   String to process
360:      * @param timestamp $date  Date to use when processing subs
361:      *
362:      * @return string  A string with all substitutions made
363:      */
364:     function substitute($string, $date = null)
365:     {
366:         if (is_null($date)) {
367:             $date = mktime(0, 0, 0);
368:         }
369:         $d = getdate($date);
370: 
371:         if (is_array($string)) {
372:             foreach (array_keys($string) as $i) {
373:                 $string[$i] = $this->substitute($string[$i], $date);
374:             }
375:             return $string;
376:         }
377: 
378:         while (preg_match('/\{dow\((\d)\,\s*(.*?)\)\}/ie',$string,$dow) > 0) {
379:             $s = strftime($dow[2], mktime(0, 0, 0, $d['mon'],
380:                                           $d['mday'] - ($d['wday'] - $dow[1]),
381:                                           $d['year']));
382: //            $s = strftime($dow[2], $date+3600-(86400*($d['wday'] - $dow[1])));
383:             $string = str_replace($dow[0], $s, $string);
384:         }
385:         $string = preg_replace('/\{i\}/i',
386:                                $this->getInstance($date),
387:                                $string);
388:         $string = preg_replace('/(?<![\134]\w)(\{[^\}]+\})/e',"strftime('\\1', $date)", $string);
389:         $string = preg_replace('/\{lc\((.*?)\)\}/ie',"strtolower('\\1')", $string);
390:         $string = preg_replace('/\{uc\((.*?)\)\}/ie',"strtoupper('\\1')", $string);
391:         $string = preg_replace('/\{t\((.*?)\)\}/ie',"trim('\\1')", $string);
392:         $string = preg_replace('/\{tl0\((.*?)\)\}/ie',"ltrim('\\1','0')", $string);
393:         $string = preg_replace('/(?<![\134]\w)\{(.*?)\}/', "\\1\\2", $string);
394: 
395:         return $string;
396:     }
397: 
398:     /**
399:      * Get the instance requested based on the date.  The instance is
400:      * determined by itype, iformat, idate, isow
401:      *
402:      * @param timestamp $date           The date the instance occurs on
403:      *
404:      * @return string                   An strftime-formatted string based on
405:      *                                  the iformat parameter
406:      */
407:     function getInstance($date)
408:     {
409:         $itype = $this->getOverride('itype', $date);
410:         $iformat = $this->getOverride('iformat', $date);
411: 
412:         // get an instance if needed
413:         $method = 'getInstance_' . $itype;
414:         if (method_exists($this, $method)) {
415:             $instance = $this->$method($date);
416:         } else {
417:             $instance = '';
418:         }
419: 
420:         return sprintf($iformat, $instance);
421:     }
422: 
423:     /**
424:      * Get an instance number for a comic that appears monthly
425:      *
426:      * @param timestamp $date  The date the comic appears
427:      *
428:      * @return integer  The instance number (unformatted)
429:      */
430:     function getInstance_monthly($date)
431:     {
432:         // get the timestamp for the first day of the month and
433:         // make sure time for $date is midnight
434:         $d = getdate($date);
435:         $date = mktime(0, 0, 0, $d['mon'], $d['mday'], $d['year']);
436:         $d = mktime(0, 0, 0, $d['mon'], 1, $d['year']);
437: 
438:         $days = $this->getOverride('days', $date, 'strtolower');
439: 
440:         // figure out how many times the comic should have appeared this month
441:         $instance = 0;
442:         while ($d <= $date) {
443:             $dow = getdate($d);
444:             $dow = substr(Horde_String::lower($d['weekday']),0,3);
445:             if (in_array($dow, $days)) {
446:                 $instance++;
447:             }
448:             $d = mktime(0, 0, 0, $dow['mon'], $dow['mday'] + 1, $dow['year']);
449:         }
450: 
451:         return $instance;
452:     }
453: 
454:     /**
455:      * Get an instance number for a comic that appears weekly
456:      *
457:      * @param timestamp $date           The date the comic appears
458:      *
459:      * @return integer                  The instance number (unformatted)
460:      */
461:     function getInstance_weekly($date)
462:     {
463:         return '';
464:     }
465: 
466:     /**
467:      * Get an instance number for a comic that appears yearly (NOT IMPLEMENTED!)
468:      *
469:      * @param timestamp $date           The date the comic appears
470:      *
471:      * @return integer                  The instance number (unformatted)
472:      */
473:     function getInstance_yearly($date)
474:     {
475:         return '';
476:     }
477: 
478:     /**
479:      * Get an instance number for a comic based on a date reference.
480:      * This takes the idate option as a reference date, then uses the
481:      * 'days' setting to determine how often it appears.  Using this
482:      * information it extrapolates which instance will occur on the
483:      * date requested.
484:      *
485:      * @param timestamp $date           The date the comic appears
486:      *
487:      * @return integer                  The instance number (unformatted)
488:      */
489:     function getInstance_ref($date)
490:     {
491:         $d = $this->getOverride('idate', $date);
492:         $c = $this->getOverride('icount', $date);
493:         $days = $this->getOverride('days', $date);
494: 
495:         if ($d < $date) {
496:             // The reference date is older than the requested date
497: 
498:             // how many full weeks can we jump?
499:             $j = floor(($date - $d)/604800);
500:             $c += $j * count($days);
501:             $d += $j * 604800;
502:             while ($d <= $date) {
503:                 $d = getdate($d);
504:                 $d = mktime(0, 0, 0, $d['mon'], $d['mday'] + 1, $d['year']);
505:                 if (in_array(Horde_String::lower(date('D', $d)), $days)) {
506:                     $c++;
507:                 }
508:             }
509:         } else {
510:             // The reference date is newer than the requested date
511: 
512:             // how many full weeks can we jump?
513:             $j = floor(($d - $date)/604800);
514:             $c -= $j * count($days);
515:             $date += $j * 604800;
516:             while ($date < $d) {
517:                 $d = getdate($d);
518:                 $d = mktime(0, 0, 0, $d['mon'], $d['mday'] - 1, $d['year']);
519:                 if (in_array(Horde_String::lower(date('D', $d)), $days)) {
520:                     $c--;
521:                 }
522:             }
523:         }
524: 
525:         return $c;
526:     }
527: }
528: 
API documentation generated by ApiGen