File_Fortune
[ class tree: File_Fortune ] [ index: File_Fortune ] [ all elements ]

Source for file Fortune.php

Documentation is available at Fortune.php

  1. <?php
  2. /*
  3.  * File_Fortune: Read and manipulate fortune databases.
  4.  *
  5.  * PHP version 5
  6.  *
  7.  * @category  File_Formats
  8.  * @package   File_Fortune
  9.  * @author    Matthew Weier O'Phinney <mweierophinney@gmail.com>, based on
  10.  *            Fortune.pm, by Greg Ward
  11.  * @copyright 2005 - 2007, Matthew Weier O'Phinney; Fortune.pm v0.2(c) 1999, Greg Ward
  12.  * @version   CVS: $Id: Fortune.php 173 2007-07-24 13:15:24Z matthew $
  13.  * @license   New BSD {@link http://www.opensource.org/licenses/bsd-license.php}
  14.  */
  15.  
  16. /** File_Fortune_Exception */
  17. require_once 'File/Fortune/Exception.php';
  18.  
  19. /**
  20.  * File_Fortune
  21.  * 
  22.  * File_Fortune: Interface to fortune cookie databases
  23.  *
  24.  * The <em>fortune</em> program is a small but important part of *nix
  25.  * culture, and this package aims to provide support for its "fortune cookie"
  26.  * databases to PHP programmers.
  27.  *
  28.  * @uses      Iterator
  29.  * @uses      Countable
  30.  * @uses      ArrayAccess
  31.  * @package   File_Fortune
  32.  * @version   @release-version@
  33.  * @tutorial  File_Fortune/File_Fortune.cls
  34.  */
  35. class File_Fortune implements IteratorCountableArrayAccess
  36. {
  37.     const VERSION = '1.0.0';
  38.  
  39.     /**
  40.      * Character that delimits fortunes (defaults to '%')
  41.      * @var string 
  42.      */
  43.     public $delim = '%';
  44.  
  45.     /**
  46.      * Length of longest string in the fortune file
  47.      * @var int 
  48.      */
  49.     public $maxLength;
  50.  
  51.     /**
  52.      * Length of shortest string in the fortune file
  53.      * @var int 
  54.      */
  55.     public $minLength;
  56.  
  57.     /**
  58.      * Version number for fortune file
  59.      * @var int 
  60.      */
  61.     public $version;
  62.  
  63.     /**
  64.      * Flag - has an operation changed the file?
  65.      * @var boolean 
  66.      */
  67.     protected $_changed = false;
  68.  
  69.     /**
  70.      * Current offset (used by iterator)
  71.      * @var int 
  72.      */
  73.     protected $_curOffset = 1;
  74.  
  75.     /**
  76.      * Directory containing fortune files
  77.      * @var string 
  78.      */
  79.     protected $_directory;
  80.  
  81.     /**
  82.      * Fortune file filehandle
  83.      * @var resource 
  84.      */
  85.     protected $_file;
  86.  
  87.     /**
  88.      * Name of fortune file to use
  89.      * @var string 
  90.      */
  91.     protected $_filename;
  92.  
  93.     /**
  94.      * Array of fortune files found in $_directory
  95.      * @var array 
  96.      */
  97.     protected $_files;
  98.  
  99.     /**
  100.      * Bit field for flags
  101.      * @var array 
  102.      */
  103.     protected $_flags;
  104.  
  105.     /**
  106.      * Array of fortunes; unset by default.
  107.      * @var array 
  108.      */
  109.     protected $_fortunes;
  110.  
  111.     /**
  112.      * Name of fortune header file (.dat file)
  113.      * @var string 
  114.      */
  115.     protected $_headerFile;
  116.  
  117.     /**
  118.      * Flag indicating whether or not a header file is present. Defaults to
  119.      * false.
  120.      * @var bool 
  121.      */
  122.     protected $_noHeader = false;
  123.  
  124.     /**
  125.      * Number of strings (fortunes) in the file
  126.      * @var int 
  127.      */
  128.     public $numstr;
  129.  
  130.     /**
  131.      * Array of file offsets (fortune indices)
  132.      * @var array 
  133.      */
  134.     protected $offsets;
  135.  
  136.     /**
  137.      * Constructor
  138.      *
  139.      * Optionally pass a filename or directory name to set the fortune file or
  140.      * directory, and, if passing a fortune file name, optionally pass the name
  141.      * of the header file.
  142.      * 
  143.      * @param  string|array$file 
  144.      * @param  string $headerFile 
  145.      * @return void 
  146.      */
  147.     public function __construct($file = null$headerFile = null)
  148.     {
  149.         if (null !== $file{
  150.             if (is_array($file)) {
  151.                 $this->setFiles($file);
  152.             elseif (is_dir($file)) {
  153.                 $this->setDirectory($file);
  154.             else {
  155.                 $this->setFile($file$headerFile);
  156.             }
  157.         }
  158.     }
  159.  
  160.     /**
  161.      * Destructor
  162.      *
  163.      * Close open files
  164.      * 
  165.      * @return void 
  166.      */
  167.     public function __destruct()
  168.     {
  169.         $this->_close();
  170.     }
  171.  
  172.     /**
  173.      * Return current fortune in iterator
  174.      * 
  175.      * @return string 
  176.      * @throws File_Fortune_Exception
  177.      */
  178.     public function current()
  179.     {
  180.         if (!empty($this->_directory|| !empty($this->_files)) {
  181.             throw new File_Fortune_Exception('Iteration over directory or multiple files not permitted');
  182.         }
  183.  
  184.         if (empty($this->_file)) {
  185.             $this->_open();
  186.         }
  187.  
  188.         if (is_array($this->_fortunes)) {
  189.             return current($this->_fortunes);
  190.         }
  191.  
  192.         return $this->offsetGet($this->_curOffset);
  193.     }
  194.  
  195.     /**
  196.      * Return current iterator key
  197.      * 
  198.      * @return int 
  199.      * @throws File_Fortune_Exception
  200.      */
  201.     public function key()
  202.     {
  203.         if (!empty($this->_directory|| !empty($this->_files)) {
  204.             throw new File_Fortune_Exception('Iteration over directory or multiple files not permitted');
  205.         }
  206.  
  207.         if (empty($this->_file)) {
  208.             $this->_open();
  209.         }
  210.  
  211.         if (is_array($this->_fortunes)) {
  212.             return key($this->_fortunes);
  213.         }
  214.  
  215.         return $this->_curOffset;
  216.     }
  217.  
  218.     /**
  219.      * Retrieve next element in iterator
  220.      * 
  221.      * @return string|false
  222.      * @throws File_Fortune_Exception
  223.      */
  224.     public function next()
  225.     {
  226.         if (!empty($this->_directory|| !empty($this->_files)) {
  227.             throw new File_Fortune_Exception('Iteration over directory or multiple files not permitted');
  228.         }
  229.  
  230.         if (empty($this->_file)) {
  231.             $this->_open();
  232.         }
  233.  
  234.         if (is_array($this->_fortunes)) {
  235.             return next($this->_fortunes);
  236.         }
  237.  
  238.         ++$this->_curOffset;
  239.         if (!$this->offsetExists($this->_curOffset)) {
  240.             return false;
  241.         }
  242.  
  243.         return $this->offsetGet($this->_curOffset);
  244.     }
  245.  
  246.     /**
  247.      * Rewind iterator to first element
  248.      * 
  249.      * @return bool 
  250.      * @throws File_Fortune_Exception
  251.      */
  252.     public function rewind()
  253.     {
  254.         if (!empty($this->_directory|| !empty($this->_files)) {
  255.             throw new File_Fortune_Exception('Iteration over directory or multiple files not permitted');
  256.         }
  257.  
  258.         if (empty($this->_file)) {
  259.             $this->_open();
  260.         }
  261.  
  262.         if (is_array($this->_fortunes)) {
  263.             return reset($this->_fortunes);
  264.         }
  265.  
  266.         $this->_curOffset = 1;
  267.         return true;
  268.     }
  269.  
  270.     /**
  271.      * Current element is valid (iterator)
  272.      * 
  273.      * @return bool 
  274.      * @throws File_Fortune_Exception
  275.      */
  276.     public function valid()
  277.     {
  278.         if (!empty($this->_directory|| !empty($this->_files)) {
  279.             throw new File_Fortune_Exception('Iteration over directory or multiple files not permitted');
  280.         }
  281.  
  282.         if (empty($this->_file)) {
  283.             $this->_open();
  284.         }
  285.  
  286.         if (is_array($this->_fortunes)) {
  287.             return key($this->_fortunes!== null;
  288.         }
  289.  
  290.         return array_key_exists($this->_curOffset$this->offsets);
  291.     }
  292.  
  293.     /**
  294.      * Count of fortunes in current active file, or of all fortunes in all files
  295.      * 
  296.      * @return int 
  297.      * @throws File_Fortune_Exception
  298.      */
  299.     public function count()
  300.     {
  301.         if (!empty($this->_directory)) {
  302.             $this->_traverseDirectory();
  303.         }
  304.         if (!empty($this->_files)) {
  305.             $count = 0;
  306.             if (!empty($this->_file)) {
  307.                 fclose($this->_file);
  308.                 $this->_file = null;
  309.             }
  310.             foreach ($this->_files as $file{
  311.                 $this->_filename = $file;
  312.                 $this->_open();
  313.                 $count += $this->numstr;
  314.                 $this->_close();
  315.             }
  316.             return $count;
  317.         }
  318.  
  319.         if (empty($this->_file)) {
  320.             $this->_open();
  321.         }
  322.  
  323.         return $this->numstr;
  324.     }
  325.  
  326.     /**
  327.      * Does fortune exist at the given offset?
  328.      * 
  329.      * @param  int $offset 
  330.      * @return boolean 
  331.      * @throws File_Fortune_Exception
  332.      */
  333.      public function offsetExists($offset)
  334.     {
  335.         if (!empty($this->_directory|| !empty($this->_files)) {
  336.             throw new File_Fortune_Exception('Array access not allowed when directory or multiple files set');
  337.         }
  338.  
  339.         return $this->_offsetExists($offset);
  340.     }
  341.  
  342.     /**
  343.      * Retrieve fortune by offset
  344.      * 
  345.      * @param  int $offset 
  346.      * @return string 
  347.      * @throws File_Fortune_Exception
  348.      */
  349.      public function offsetGet($offset)
  350.     {
  351.         if (!empty($this->_directory|| !empty($this->_files)) {
  352.             throw new File_Fortune_Exception('Array access not allowed when directory or multiple files set');
  353.         }
  354.  
  355.         return $this->_getOne($offset);
  356.     }
  357.  
  358.     /**
  359.      * Set a fortune at a given offset
  360.      * 
  361.      * @param  int $offset 
  362.      * @param  string $value 
  363.      * @return void 
  364.      * @throws File_Fortune_Exception
  365.      */
  366.      public function offsetSet($offset$value)
  367.     {
  368.         if (!empty($this->_directory|| !empty($this->_files)) {
  369.             throw new File_Fortune_Exception('Array access not allowed when directory or multiple files set');
  370.         }
  371.  
  372.         $this->_initChanges();
  373.         if (!$this->offsetExists($offset)) {
  374.             throw new File_Fortune_Exception('Cannot modify fortune; offset does not exist');
  375.         }
  376.         $this->_fortunes[$offset - 1$value;
  377.     }
  378.  
  379.     /**
  380.      * Delete a fortune at a given offset
  381.      * 
  382.      * @param  int $offset 
  383.      * @return void 
  384.      * @throws File_Fortune_Exception
  385.      */
  386.      public function offsetUnset($offset)
  387.     {
  388.         if (!empty($this->_directory|| !empty($this->_files)) {
  389.             throw new File_Fortune_Exception('Array access not allowed when directory or multiple files set');
  390.         }
  391.  
  392.         if (!$this->offsetExists($offset)) {
  393.             throw new File_Fortune_Exception('Cannot delete fortune; offset does not exist');
  394.         }
  395.         $this->_initChanges();
  396.         unset($this->_fortunes[$offset - 1]);
  397.         $this->_fortunes = array_merge(array()$this->_fortunes);
  398.         $this->numstr = count($this->_fortunes);
  399.     }
  400.  
  401.     /**
  402.      * Set fortune file
  403.      *
  404.      * Setting a file overwrites any directory set as well.
  405.      * 
  406.      * @param  string $file 
  407.      * @param  string $headerFile 
  408.      * @return File_Fortune 
  409.      */
  410.     public function setFile($file$headerFile = null)
  411.     {
  412.         if ($file != $this->_filename{
  413.             $this->_close();
  414.         }
  415.         if (!empty($this->_directory)) {
  416.             $this->_directory = null;
  417.         }
  418.  
  419.         $this->_filename = (string) $file;
  420.  
  421.         $this->setHeaderFile($headerFile);
  422.         return $this;
  423.     }
  424.  
  425.     /**
  426.      * Retrieve current fortune file name
  427.      * 
  428.      * @return string 
  429.      */
  430.     public function getFile()
  431.     {
  432.         return $this->_filename;
  433.     }
  434.  
  435.     /**
  436.      * Set header file name
  437.      * 
  438.      * @param  string $headerFile 
  439.      * @return File_Fortune 
  440.      */
  441.     public function setHeaderFile($headerFile)
  442.     {
  443.         $this->_headerFile = (string) $headerFile;
  444.         return $this;
  445.     }
  446.  
  447.     /**
  448.      * Retrieve current header file name
  449.      * 
  450.      * @return string 
  451.      */
  452.     public function getHeaderFile()
  453.     {
  454.         return $this->_headerFile;
  455.     }
  456.  
  457.     /**
  458.      * Set one or more files to search for fortunes
  459.      * 
  460.      * @return File_Fortune 
  461.      * @throws File_Fortune_Exception
  462.      */
  463.     public function setFiles()
  464.     {
  465.         if (0 == func_num_args()) {
  466.             throw new File_Fortune_Exception('setFiles() expects at least one argument');
  467.         }
  468.  
  469.         $this->_close();
  470.         $this->_directory = null;
  471.         $this->_filename  = null;
  472.         $this->_fortunes  = null;
  473.  
  474.         $argv func_get_args();
  475.         $spec array_shift($argv);
  476.         if (is_array($spec)) {
  477.             $this->_files = $spec;
  478.             return $this;
  479.         }
  480.         $this->_files = array((string) $spec);
  481.         foreach ($argv as $file{
  482.             $this->_files[= (string) $file;
  483.         }
  484.  
  485.         return $this;
  486.     }
  487.  
  488.     /**
  489.      * Return list of fortune files in use
  490.      *
  491.      * Returns a list of fortune files in use, either as registered via
  492.      * {@link setFiles()} or as determined by traversing the directory set via
  493.      * {@link setDirectory()}.
  494.      * 
  495.      * @return array 
  496.      */
  497.     public function getFiles()
  498.     {
  499.         if ((null === $this->_files&& !empty($this->_directory)) {
  500.             $this->_traverseDirectory();
  501.         }
  502.         return $this->_files;
  503.     }
  504.  
  505.     /**
  506.      * Set directory from which to randomly select a fortune file
  507.      * 
  508.      * @param  string $directory 
  509.      * @return File_Fortune 
  510.      */
  511.     public function setDirectory($directory)
  512.     {
  513.         $this->_close();
  514.  
  515.         $this->_directory = $directory;
  516.         $this->_files     = null;
  517.         $this->_fortunes  = null;
  518.         $this->_filename  = null;
  519.  
  520.         return $this;
  521.     }
  522.  
  523.     /**
  524.      * Retrieve current directory of fortune files
  525.      * 
  526.      * @return string 
  527.      */
  528.     public function getDirectory()
  529.     {
  530.         return $this->_directory;
  531.     }
  532.  
  533.     /**
  534.      * Add a new fortune
  535.      * 
  536.      * @param  string $fortune 
  537.      * @return File_Fortune 
  538.      * @throws File_Fortune_Exception
  539.      */
  540.     public function add($fortune)
  541.     {
  542.         if (!empty($this->_directory|| !empty($this->_files)) {
  543.             throw new File_Fortune_Exception('Cannot add fortune when directory or multiple files specified');
  544.         }
  545.  
  546.         $this->_initChanges();
  547.         $this->_fortunes[$fortune;
  548.         $this->numstr = count($this->_fortunes);
  549.         return $this;
  550.     }
  551.  
  552.     /**
  553.      * Update an existing fortune
  554.      * 
  555.      * @param  int $index 
  556.      * @param  string $fortune 
  557.      * @return void 
  558.      * @throws File_Fortune_Exception
  559.      */
  560.     public function update($index$fortune)
  561.     {
  562.         if (!empty($this->_directory|| !empty($this->_files)) {
  563.             throw new File_Fortune_Exception('Cannot update fortune when directory or multiple files specified');
  564.         }
  565.  
  566.         $this->offsetSet($index$fortune);
  567.         return $this;
  568.     }
  569.  
  570.     /**
  571.      * Delete an existing fortune
  572.      * 
  573.      * @param  int $index 
  574.      * @return void 
  575.      * @throws File_Fortune_Exception
  576.      */
  577.     public function delete($index)
  578.     {
  579.         if (!empty($this->_directory|| !empty($this->_files)) {
  580.             throw new File_Fortune_Exception('Cannot delete fortune when directory or multiple files specified');
  581.         }
  582.  
  583.         $this->offsetUnset($index);
  584.         return $this;
  585.     }
  586.  
  587.     /**
  588.      * Retrieve random fortune
  589.      * 
  590.      * @return string 
  591.      */
  592.     public function getRandom()
  593.     {
  594.         if (empty($this->_file|| !empty($this->_directory)) {
  595.             $this->_open();
  596.         }
  597.  
  598.         if (is_array($this->_fortunes)) {
  599.             $offset array_rand(array_keys($this->_fortunes));
  600.         else {
  601.             do {
  602.                 $offset array_rand($this->offsets);
  603.             while ($offset < 1);
  604.         }
  605.  
  606.         return $this->_getOne($offset);
  607.     }
  608.  
  609.     /**
  610.      * Retrieve all fortunes from the current file or set of files
  611.      * 
  612.      * @return array 
  613.      */
  614.     public function getAll()
  615.     {
  616.         if (!empty($this->_directory)) {
  617.             $this->_traverseDirectory();
  618.         }
  619.  
  620.         if (!empty($this->_files)) {
  621.             if (!is_array($this->_fortunes)) {
  622.                 $fortunes = array();
  623.                 if (!empty($this->_file)) {
  624.                     fclose($this->_file);
  625.                     $this->_file = null;
  626.                 }
  627.                 foreach ($this->_files as $file{
  628.                     $this->_filename = $file;
  629.                     $fortunes array_merge($fortunes$this->_getAll());
  630.                     fclose($this->_file);
  631.                     $this->_file = null;
  632.                 }
  633.                 $this->_fortunes = $fortunes;
  634.             }
  635.         else {
  636.             if (!is_array($this->_fortunes)) {
  637.                 $this->_fortunes = $this->_getAll();
  638.             }
  639.         }
  640.  
  641.         return $this->_fortunes;
  642.     }
  643.  
  644.     /**
  645.      * Save changes
  646.      * 
  647.      * @return void 
  648.      * @throws File_Fortune_Exception
  649.      */
  650.     public function save()
  651.     {
  652.         if (!empty($this->_directory|| !empty($this->_files)) {
  653.             throw new File_Fortune_Exception('Cannot save changes when directory or multiple files set');
  654.         }
  655.  
  656.         $filename $this->getFile();
  657.         $this->_close();
  658.         $this->setFile($filename);
  659.     }
  660.  
  661.     /**
  662.      * Create a new fortune file from an array of fortunes
  663.      * 
  664.      * @param  array $fortunes 
  665.      * @param  string $file 
  666.      * @return void 
  667.      * @throws File_Fortune_Exception
  668.      */
  669.     public function create(array $fortunes$file = null)
  670.     {
  671.         if ((null !== $file&& ($file != $this->getFile())) {
  672.             $this->setFile($file);
  673.         else {
  674.             $file $this->getFile();
  675.         }
  676.         if (null === $file{
  677.             throw new File_Fortune_Exception('Cannot create fortune file; no file specified');
  678.         }
  679.         if (file_exists($file)) {
  680.             throw new File_Fortune_Exception('Cannot create fortune file; already exists');
  681.         }
  682.  
  683.         $this->_fortunes = $fortunes;
  684.         $this->_save();
  685.         $this->_close();
  686.     }
  687.  
  688.     /**
  689.      * Open a fortune file
  690.      * 
  691.      * @return void 
  692.      * @throws File_Fortune_Exception
  693.      */
  694.     protected function _open()
  695.     {
  696.         if (!empty($this->_file&& empty($this->_directory&& empty($this->_files)) {
  697.             return;
  698.         }
  699.  
  700.         $this->_close();
  701.  
  702.         if ($this->_directory || !empty($this->_files)) {
  703.             // get random file from directory or file list
  704.             $this->_getRandomFile();
  705.         }
  706.  
  707.         if (empty($this->_filename)) {
  708.             throw new File_Fortune_Exception('No file or directory set');
  709.         }
  710.  
  711.         if (!is_readable($this->_filename)) {
  712.             throw new File_Fortune_Exception('Fortune file "' $this->_filename . '" not readable');
  713.         }
  714.  
  715.         if (empty($this->_headerFilename)) {
  716.             $this->setHeaderFile($this->_filename . '.dat');
  717.         }
  718.  
  719.         if (false === ($this->_file = fopen($this->_filename'r'))) {
  720.             throw new File_Fortune_Exception('Unable to read fortune file "' $this->_filename . '"');
  721.         }
  722.  
  723.         $this->_readHeader();
  724.     }
  725.  
  726.     /**
  727.      * Traverse a registered directory to get a list of files
  728.      * 
  729.      * @return void 
  730.      * @throws File_Fortune_Exception
  731.      */
  732.     protected function _traverseDirectory()
  733.     {
  734.         if (empty($this->_files)) {
  735.             if (!is_dir($this->_directory|| !is_readable($this->_directory)) {
  736.                 throw new File_Fortune_Exception('Directory "' $this->_directory . '" is invalid or unreadable');
  737.             }
  738.  
  739.             $directory = new DirectoryIterator($this->_directory);
  740.             $files     = array();
  741.             foreach ($directory as $file{
  742.                 if ($file->isDot(|| $file->isDir()) {
  743.                     continue;
  744.                 }
  745.  
  746.                 $filename $file->getFilename();
  747.                 if ('.dat' == substr($filename-4)) {
  748.                     continue;
  749.                 }
  750.  
  751.                 $files[$file->getPathName();
  752.             }
  753.             $this->_files = $files;
  754.         }
  755.     }
  756.  
  757.     /**
  758.      * Randomly select a fortune file from a registered directory
  759.      * 
  760.      * @return void 
  761.      */
  762.     protected function _getRandomFile()
  763.     {
  764.         $this->_traverseDirectory();
  765.         $index array_rand($this->_files);
  766.  
  767.         $this->_fortunes = null;
  768.         $this->_filename = $this->_files[$index];
  769.     }
  770.  
  771.     /**
  772.      * Read a fortune database header file
  773.      * 
  774.      * Reads the header file associated with this fortune database.  The name of
  775.      * the header file is determined by the {@link __construct() constructor}:
  776.      * either it is based on the name of the data file, or supplied by the
  777.      * caller.
  778.      * 
  779.      * If the header file does not exist, this function calls
  780.      * {@link _computeHeader()} automatically, which has the same effect as
  781.      * reading the header from a file.
  782.      * 
  783.      * The header contains the following values, which are stored as attributes
  784.      * of the {@link File_Fortune} object:
  785.      * <ul>
  786.      *     <li>{@link $version}; version number</li>
  787.      *     <li>{@link $numstr}; number of strings (fortunes) in the file</li>
  788.      *     <li>{@link $maxLength}; length of longest string in the file</li>
  789.      *     <li>{@link $minLength}; length of shortest string in the file</li>
  790.      *     <li>{@link $flags}; bit field for flags (see strfile(1) man
  791.      *     page)</li>
  792.      *     <li>{@link $delim}; character that delimits fortunes</li>
  793.      * </ul>
  794.      *
  795.      * {@link $numstr} is available via the count() function (count($fortunes));
  796.      * if you're interested in the others, you'll have to go grubbing through
  797.      * the File_Fortune object, e.g.:
  798.      *
  799.      * <code>
  800.      * $fortune_file = new Fortune('fortunes');
  801.      * $delim        = $fortune_file->delim;
  802.      * $maxLength    = $fortune_file->maxLength;
  803.      * </code>
  804.      *
  805.      * @return void 
  806.      * @throws File_Fortune_Exception
  807.      */
  808.     protected function _readHeader()
  809.     {
  810.         $headerFile $this->getHeaderFile();
  811.         if (!file_exists($headerFile)) {
  812.             return $this->_computeHeader();
  813.         }
  814.       
  815.         if (false === ($file fopen($headerFile'r'))) {
  816.             throw new File_Fortune_Exception('Unable to read header file "' $headerFile '"');
  817.         }
  818.  
  819.         /* 
  820.          * from the strfile(1) man page:
  821.          *       unsigned long str_version;  // version number
  822.          *       unsigned long str_numstr;   // # of strings in the file
  823.          *       unsigned long str_longlen;  // length of longest string
  824.          *       unsigned long str_shortlen; // shortest string length
  825.          *       unsigned long str_flags;    // bit field for flags
  826.          *       char str_delim;             // delimiting character
  827.          * that 'char' is padded out to a full word, so the header is 24 bytes
  828.          */
  829.  
  830.         if (false === ($header fread($file24))) {
  831.             throw new File_Fortune_Exception('Malformed header file "' $headerFile '"');
  832.         }
  833.  
  834.         $values unpack('N5nums/adelim/xxx'$header);
  835.         if (empty($values|| (6 != count($values))) {
  836.             throw new File_Fortune_Exception('Failed to load full header');
  837.         }
  838.  
  839.         $this->version    = $values['nums1'];
  840.         $this->numstr     = $values['nums2'];
  841.         $this->maxLength  = $values['nums3'];
  842.         $this->minLength  = $values['nums4'];
  843.         $this->flags      = $values['nums5'];
  844.         $this->delim      = $values['delim'];
  845.  
  846.         $expectedOffsets $this->numstr + 1;
  847.  
  848.         $amountData = 4 * $expectedOffsets;
  849.  
  850.         if (false === ($data fread($file$amountData))) {
  851.             throw new File_Fortune_Exception('Failed to read offsets for all fortunes');
  852.         }
  853.         $offsets array_merge(unpack('N*'$data));
  854.         if (count($offsets!= $expectedOffsets{
  855.             throw new File_Fortune_Exception('Expected header offsets do not match actual offsets');
  856.         }
  857.         $this->offsets = $offsets;
  858.  
  859.         fclose($file);
  860.     }
  861.  
  862.     /**
  863.      * Compute fortune file header information
  864.      *
  865.      * Reads the contents of the fortune file and computes the header
  866.      * information that would normally be found in a header (.dat) file and read
  867.      * by {@link _readHeader()}.  This is useful if you maintain a file of
  868.      * fortunes by hand and do not have the corresponding data file.
  869.      * 
  870.      * An optional delimiter argument may be passed to this function; if
  871.      * present, that delimiter will be used to separate entries in the fortune
  872.      * file.  If not provided, a percent sign ("%") will be used (this is the
  873.      * standard fortune file delimiter).
  874.      *
  875.      * Additionally, the {@link $noHeader} property will be set to true.
  876.      *
  877.      * <b>NOTE:</b> It is most efficient to use fortune files that have header
  878.      * files. In order to get offsets and fortunes, this method actually must
  879.      * read the entire fortune file, and stores all fortunes in the
  880.      * {@link $_fortunes} array.
  881.      *
  882.      * @param  string $delim Defaults to '%'
  883.      * @return void 
  884.      * @throws File_Fortune_Exception
  885.      */
  886.     protected function _computeHeader($delim '%')
  887.     {
  888.         $filename $this->getFile();
  889.         if (false === ($contents file_get_contents($filename))) {
  890.             throw new File_Fortune_Exception(
  891.                 'Unable to read fortune file (' $filename ') to compute headers'
  892.             );
  893.         }
  894.  
  895.         // Get all fortunes
  896.         // Strip off final delimiter
  897.         $contents substr_replace($contents''strrpos($contents"$delim\n"));
  898.         $fortunes explode("$delim\n"$contents)// explode into array
  899.  
  900.         // Get offsets, min, max, etc.
  901.         $offsets   = array(0);
  902.         $numstr    count($fortunes);
  903.         $curOffset = 0;
  904.         $delimLen  strlen($delim);
  905.         $min       = null;
  906.         $max       = null;
  907.         for ($i = 0; $i $numstr$i++{
  908.             $len        strlen($fortunes[$i]);
  909.             $curOffset += $len $delimLen + 1;
  910.             $offsets[]  $curOffset;
  911.             if (empty($min|| ($min $len)) {
  912.                 $min $len;
  913.             elseif (empty($max|| ($max $len)) {
  914.                 $max $len;
  915.             }
  916.         }
  917.  
  918.         // Set object properties
  919.         $this->version    = 1;
  920.         $this->numstr     = $numstr;
  921.         $this->maxLength  = $max;
  922.         $this->minLength  = $min;
  923.         $this->flags      = 0;
  924.         $this->delim      = $delim;
  925.         $this->offsets    = $offsets;
  926.         $this->_fortunes  = $fortunes;
  927.         $this->noHeader   = true;
  928.     }
  929.  
  930.     /**
  931.      * Close fortune file
  932.      *
  933.      * Closes the fortune file if it's open, and unsets all header-related
  934.      * properties (except {@link $delim}); does nothing otherwise.
  935.      * 
  936.      * @return void 
  937.      */
  938.     protected function _close()
  939.     {
  940.         if (!empty($this->_file)) {
  941.             fclose($this->_file);
  942.             $this->_file = null;
  943.  
  944.             if ($this->_changed{
  945.                 $this->_save();
  946.             }
  947.  
  948.             unset(
  949.                 $this->flags,
  950.                 $this->maxLength,
  951.                 $this->minLength,
  952.                 $this->numstr,
  953.                 $this->offsets,
  954.                 $this->version
  955.             );
  956.  
  957.             $this->delim     = '%';
  958.             $this->_changed  = false;
  959.             $this->_fortunes = null;
  960.         }
  961.     }
  962.  
  963.     /**
  964.      * Read a fortune from the file
  965.      *
  966.      * Reads a fortune directly from the file. If an error occurs, an exception
  967.      * is thrown. Otherwise, on sucess, the fortune is returned.
  968.      *
  969.      * @param int 
  970.      * @return string 
  971.      * @throws File_Fortune_Exception
  972.      */
  973.     protected function _readFromFile($offset
  974.     {
  975.         $start  $this->offsets[$offset - 1];
  976.         $end    $this->offsets[$offset];
  977.         $length $end $start;
  978.  
  979.         // decrement length 2 bytes for most fortunes (to drop trailing "%\n"),
  980.         // and none for the last one (keep trailing newline)
  981.         $delimLength strlen($this->delim| 1;
  982.         $length -= ($offset == $this->numstr? 0 : ($delimLength + 2);
  983.  
  984.         if (-1 == fseek($this->_file$start)) {
  985.             throw new File_Fortune_Exception('Unable to seek to fortune offset');
  986.         }
  987.         $fortune fread($this->_file$length);
  988.  
  989.         return $fortune;
  990.     }
  991.  
  992.     /**
  993.      * Initialize changes
  994.      * 
  995.      * @return void 
  996.      */
  997.     protected function _initChanges()
  998.     {
  999.         $this->_changed = true;
  1000.         if (!is_array($this->_fortunes)) {
  1001.             $this->getAll();
  1002.         }
  1003.     }
  1004.  
  1005.     /**
  1006.      * Save changes to disc
  1007.      *
  1008.      * Changes to fortunes happen in-memory; this method saves them to disc.
  1009.      * 
  1010.      * @return void 
  1011.      * @throws File_Fortune_Exception
  1012.      */
  1013.     protected function _save()
  1014.     {
  1015.         $this->_initChanges();
  1016.  
  1017.         if (!empty($this->_file)) {
  1018.             fclose($this->_file);
  1019.             $this->_file = null;
  1020.         }
  1021.  
  1022.         if (false === ($fh fopen($this->_filename'w'))) {
  1023.             throw new File_Fortune_Exception(
  1024.                 'Unable to open "' $this->filename . '" to write'
  1025.             );
  1026.         }
  1027.         if (!flock($fhLOCK_EX)) {
  1028.             throw new File_Fortune_Exception(
  1029.                 'Unable to obtain file lock for writing'
  1030.             );
  1031.         }
  1032.  
  1033.         // Write file
  1034.         $offsets = array(0)// obtain offsets as we go
  1035.         $min     = null;     // For determining shortest fortune
  1036.         $max     = null;     // For determining longest fortune
  1037.         $numstr  count($this->_fortunes);
  1038.         for ($i = 0; $i $numstr; ++$i{
  1039.             $fortune $this->_fortunes[$i];
  1040.             fwrite($fh$fortune);
  1041.             if ($i != ($numstr - 1)) {
  1042.                 fwrite($fh"\n" $this->delim . "\n");
  1043.             else {
  1044.                 fwrite($fh"\n");
  1045.             }
  1046.             if (false === ($pos ftell($fh))) {
  1047.                 throw new File_Fortune_Exception(
  1048.                     'Unable to obtain file position while writing fortunes'
  1049.                 );
  1050.             }
  1051.  
  1052.             // Determine if this is the min or max length fortune
  1053.             $len     strlen($fortune);
  1054.             if (empty($min|| ($len $min)) 
  1055.                 $min $len;
  1056.             
  1057.  
  1058.             if (empty($max|| ($len $max)) 
  1059.                 $max $len;
  1060.             }
  1061.  
  1062.             // Add offset to list
  1063.             $offsets[$pos;
  1064.         }
  1065.         flock($fhLOCK_UN);
  1066.         fclose($fh);
  1067.  
  1068.         // Create header block
  1069.         // Set version
  1070.         if (empty($this->version)) {
  1071.             $this->version = 1;
  1072.         else {
  1073.             ++$this->version;
  1074.         }
  1075.  
  1076.         // Set other header attributes
  1077.         $this->offsets   = $offsets;         // list of offsets
  1078.         $this->numstr    = count($this->_fortunes)// number of fortunes
  1079.         $this->maxLength = $max;             // longest fortune
  1080.         $this->minLength = $min;             // shortest fortune
  1081.         if (empty($this->flags)) {
  1082.             $this->flags = 0;
  1083.         }
  1084.  
  1085.         // Write header file
  1086.         $this->_writeHeader();
  1087.     }
  1088.  
  1089.     /**
  1090.      * Write the header file for a fortune file
  1091.      *
  1092.      * Writes a fortune header file to {@link $headerFilename}, using
  1093.      * {@link $version}{@link $numstr}{@link $maxLength},
  1094.      * {@link $minLength}{@link $flags}{@link $delim}, and {@link $offsets}.
  1095.      *
  1096.      * If an error occurs, an exception is thrown.
  1097.      *
  1098.      * @return void 
  1099.      * @throws File_Fortune_Exception
  1100.      */
  1101.     protected function _writeHeader(
  1102.     {
  1103.         $headerFile $this->getHeaderFile();
  1104.         if (empty($headerFile)) {
  1105.             $headerFile $this->getFile('.dat';
  1106.             $this->setHeaderFile($headerFile);
  1107.         }
  1108.  
  1109.         $header pack(
  1110.             'NNNNNaxxx'
  1111.             $this->version,
  1112.             $this->numstr,
  1113.             $this->maxLength,
  1114.             $this->minLength,
  1115.             $this->flags,
  1116.             $this->delim
  1117.         );
  1118.  
  1119.         // Pack offsets
  1120.         $offsetBin '';
  1121.         foreach ($this->offsets as $offset{
  1122.             $offsetBin .= pack('N'$offset);
  1123.         }
  1124.  
  1125.         // Write header file
  1126.         if (false === ($fh fopen($headerFile'w'))) {
  1127.             throw new File_Fortune_Exception(
  1128.                 'Unable to open header file "' $headerFile '" for writing'
  1129.             );
  1130.         }
  1131.         if (!flock($fhLOCK_EX)) {
  1132.             throw new File_Fortune_Exception(
  1133.                 'Unable to obtain lock on header file'
  1134.             );
  1135.         }
  1136.         fwrite($fh$header $offsetBin);
  1137.         flock($fhLOCK_UN);
  1138.         fclose($fh);
  1139.     }
  1140.  
  1141.     /**
  1142.      * Retrieve by offset in file
  1143.      * 
  1144.      * @param  int $offset 
  1145.      * @return boolean 
  1146.      */
  1147.     protected function _offsetExists($offset)
  1148.     {
  1149.         if (empty($this->_file)) {
  1150.             $this->_open();
  1151.         }
  1152.  
  1153.         if (is_array($this->_fortunes&& isset($this->_fortunes[$offset - 1])) {
  1154.             return true;
  1155.         elseif (!is_array($this->_fortunes
  1156.             && isset($this->offsets[$offset])
  1157.             && isset($this->offsets[$offset - 1])) 
  1158.         {
  1159.             return true;
  1160.         }
  1161.  
  1162.         return false;
  1163.     }
  1164.  
  1165.     /**
  1166.      * Retrieve a single fortune by offset
  1167.      * 
  1168.      * @param  int $offset 
  1169.      * @return string 
  1170.      * @throws File_Fortune_Exception
  1171.      */
  1172.     protected function _getOne($offset)
  1173.     {
  1174.         if ($this->_offsetExists($offset)) {
  1175.             if (is_array($this->_fortunes)) {
  1176.                 return $this->_fortunes[$offset - 1];
  1177.             }
  1178.  
  1179.             return $this->_readFromFile($offset);
  1180.         }
  1181.  
  1182.         throw new File_Fortune_Exception('Requested fortune index does not exist');
  1183.     }
  1184.  
  1185.     /**
  1186.      * Retrieve all fortunes from the current active file
  1187.      * 
  1188.      * @return void 
  1189.      */
  1190.     protected function _getAll()
  1191.     {
  1192.         if (empty($this->_file)) {
  1193.             $this->_open();
  1194.         }
  1195.  
  1196.         $fortunes = array();
  1197.         for ($i = 1; $i <= $this->numstr; ++$i{
  1198.             $fortunes[$this->_readFromFile($i);
  1199.         }
  1200.  
  1201.         return $fortunes;
  1202.     }
  1203. }
  1204.  
  1205. /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

Documentation generated on Mon, 11 Mar 2019 15:09:20 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.