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 130 2007-07-05 21:09:19Z 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 = '0.9.2';
  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 $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_dir($file)) {
  151.                 $this->setDirectory($file);
  152.             else {
  153.                 $this->setFile($file$headerFile);
  154.             }
  155.         }
  156.     }
  157.  
  158.     /**
  159.      * Destructor
  160.      *
  161.      * Close open files
  162.      * 
  163.      * @return void 
  164.      */
  165.     public function __destruct()
  166.     {
  167.         $this->_close();
  168.     }
  169.  
  170.     /**
  171.      * Return current fortune in iterator
  172.      * 
  173.      * @return string 
  174.      */
  175.     public function current()
  176.     {
  177.         if (empty($this->_file)) {
  178.             $this->_open();
  179.         }
  180.  
  181.         if (is_array($this->_fortunes)) {
  182.             return current($this->_fortunes);
  183.         }
  184.  
  185.         return $this->offsetGet($this->_curOffset);
  186.     }
  187.  
  188.     /**
  189.      * Return current iterator key
  190.      * 
  191.      * @return int 
  192.      */
  193.     public function key()
  194.     {
  195.         if (empty($this->_file)) {
  196.             $this->_open();
  197.         }
  198.  
  199.         if (is_array($this->_fortunes)) {
  200.             return key($this->_fortunes);
  201.         }
  202.  
  203.         return $this->_curOffset;
  204.     }
  205.  
  206.     /**
  207.      * Retrieve next element in iterator
  208.      * 
  209.      * @return string|false
  210.      */
  211.     public function next()
  212.     {
  213.         if (empty($this->_file)) {
  214.             $this->_open();
  215.         }
  216.  
  217.         if (is_array($this->_fortunes)) {
  218.             return next($this->_fortunes);
  219.         }
  220.  
  221.         ++$this->_curOffset;
  222.         if (!$this->offsetExists($this->_curOffset)) {
  223.             return false;
  224.         }
  225.  
  226.         return $this->offsetGet($this->_curOffset);
  227.     }
  228.  
  229.     /**
  230.      * Rewind iterator to first element
  231.      * 
  232.      * @return bool 
  233.      */
  234.     public function rewind()
  235.     {
  236.         if (empty($this->_file)) {
  237.             $this->_open();
  238.         }
  239.  
  240.         if (is_array($this->_fortunes)) {
  241.             return reset($this->_fortunes);
  242.         }
  243.  
  244.         $this->_curOffset = 1;
  245.         return true;
  246.     }
  247.  
  248.     /**
  249.      * Current element is valid (iterator)
  250.      * 
  251.      * @return bool 
  252.      */
  253.     public function valid()
  254.     {
  255.         if (empty($this->_file)) {
  256.             $this->_open();
  257.         }
  258.  
  259.         if (is_array($this->_fortunes)) {
  260.             return key($this->_fortunes!== null;
  261.         }
  262.  
  263.         return array_key_exists($this->_curOffset$this->offsets);
  264.     }
  265.  
  266.     /**
  267.      * Count of fortunes in current active file
  268.      * 
  269.      * @return int 
  270.      */
  271.     public function count()
  272.     {
  273.         if (empty($this->_file)) {
  274.             $this->_open();
  275.         }
  276.  
  277.         return $this->numstr;
  278.     }
  279.  
  280.     /**
  281.      * Does fortune exist at the given offset?
  282.      * 
  283.      * @param  int $offset 
  284.      * @return boolean 
  285.      */
  286.      public function offsetExists($offset)
  287.     {
  288.         if (empty($this->_file)) {
  289.             $this->_open();
  290.         }
  291.  
  292.         if (is_array($this->_fortunes&& isset($this->_fortunes[$offset - 1])) {
  293.             return true;
  294.         elseif (!is_array($this->_fortunes
  295.             && isset($this->offsets[$offset])
  296.             && isset($this->offsets[$offset - 1])) 
  297.         {
  298.             return true;
  299.         }
  300.  
  301.         return false;
  302.     }
  303.  
  304.     /**
  305.      * Retrieve fortune by offset
  306.      * 
  307.      * @param  int $offset 
  308.      * @return string 
  309.      */
  310.      public function offsetGet($offset)
  311.     {
  312.         if ($this->offsetExists($offset)) {
  313.             if (is_array($this->_fortunes)) {
  314.                 return $this->_fortunes[$offset - 1];
  315.             }
  316.  
  317.             return $this->_readFromFile($offset);
  318.         }
  319.  
  320.         throw new File_Fortune_Exception('Requested fortune index does not exist');
  321.     }
  322.  
  323.     /**
  324.      * Set a fortune at a given offset
  325.      * 
  326.      * @param  int $offset 
  327.      * @param  string $value 
  328.      * @return void 
  329.      */
  330.      public function offsetSet($offset$value)
  331.     {
  332.         $this->_initChanges();
  333.         if (!$this->offsetExists($offset)) {
  334.             throw new File_Fortune_Exception('Cannot modify fortune; offset does not exist');
  335.         }
  336.         $this->_fortunes[$offset - 1$value;
  337.     }
  338.  
  339.     /**
  340.      * Delete a fortune at a given offset
  341.      * 
  342.      * @param  int $offset 
  343.      * @return void 
  344.      */
  345.      public function offsetUnset($offset)
  346.     {
  347.         if (!$this->offsetExists($offset)) {
  348.             throw new File_Fortune_Exception('Cannot delete fortune; offset does not exist');
  349.         }
  350.         $this->_initChanges();
  351.         unset($this->_fortunes[$offset - 1]);
  352.         $this->_fortunes = array_merge(array()$this->_fortunes);
  353.         $this->numstr = count($this->_fortunes);
  354.     }
  355.  
  356.     /**
  357.      * Set fortune file
  358.      * 
  359.      * @param  string $file 
  360.      * @param  string $headerFile 
  361.      * @return File_Fortune 
  362.      */
  363.     public function setFile($file$headerFile = null)
  364.     {
  365.         if ($file != $this->_filename{
  366.             $this->_close();
  367.         }
  368.         $this->_filename = (string) $file;
  369.  
  370.         $this->setHeaderFile($headerFile);
  371.         return $this;
  372.     }
  373.  
  374.     /**
  375.      * Retrieve current fortune file name
  376.      * 
  377.      * @return string 
  378.      */
  379.     public function getFile()
  380.     {
  381.         return $this->_filename;
  382.     }
  383.  
  384.     /**
  385.      * Set header file name
  386.      * 
  387.      * @param  string $headerFile 
  388.      * @return File_Fortune 
  389.      */
  390.     public function setHeaderFile($headerFile)
  391.     {
  392.         $this->_headerFile = (string) $headerFile;
  393.         return $this;
  394.     }
  395.  
  396.     /**
  397.      * Retrieve current header file name
  398.      * 
  399.      * @return string 
  400.      */
  401.     public function getHeaderFile()
  402.     {
  403.         return $this->_headerFile;
  404.     }
  405.  
  406.     /**
  407.      * Set one or more files to search for fortunes
  408.      * 
  409.      * @return File_Fortune 
  410.      */
  411.     public function setFiles()
  412.     {
  413.         if (0 == func_num_args()) {
  414.             throw new File_Fortune_Exception('setFiles() expects at least one argument');
  415.         }
  416.  
  417.         $argv func_get_args();
  418.         $spec array_shift($argv);
  419.         if (is_array($spec)) {
  420.             $this->_files = $spec;
  421.             return $this;
  422.         }
  423.         $this->_files = array((string) $spec);
  424.         foreach ($argv as $file{
  425.             $this->_files[= (string) $file;
  426.         }
  427.  
  428.         return $this;
  429.     }
  430.  
  431.     /**
  432.      * Return list of fortune files in use
  433.      *
  434.      * Returns a list of fortune files in use, either as registered via
  435.      * {@link setFiles()} or as determined by traversing the directory set via
  436.      * {@link setDirectory()}.
  437.      * 
  438.      * @return array 
  439.      */
  440.     public function getFiles()
  441.     {
  442.         if ((null === $this->_files&& !empty($this->_directory)) {
  443.             $this->_traverseDirectory();
  444.         }
  445.         return $this->_files;
  446.     }
  447.  
  448.     /**
  449.      * Set directory from which to randomly select a fortune file
  450.      * 
  451.      * @param  string $directory 
  452.      * @return File_Fortune 
  453.      */
  454.     public function setDirectory($directory)
  455.     {
  456.         $this->_close();
  457.  
  458.         $this->_directory = $directory;
  459.         $this->_files     = null;
  460.         return $this;
  461.     }
  462.  
  463.     /**
  464.      * Retrieve current directory of fortune files
  465.      * 
  466.      * @return string 
  467.      */
  468.     public function getDirectory()
  469.     {
  470.         return $this->_directory;
  471.     }
  472.  
  473.     /**
  474.      * Add a new fortune
  475.      * 
  476.      * @param  string $fortune 
  477.      * @return File_Fortune 
  478.      */
  479.     public function add($fortune)
  480.     {
  481.         $this->_initChanges();
  482.         $this->_fortunes[$fortune;
  483.         $this->numstr = count($this->_fortunes);
  484.         return $this;
  485.     }
  486.  
  487.     /**
  488.      * Update an existing fortune
  489.      * 
  490.      * @param  int $index 
  491.      * @param  string $fortune 
  492.      * @return void 
  493.      */
  494.     public function update($index$fortune)
  495.     {
  496.         $this->offsetSet($index$fortune);
  497.         return $this;
  498.     }
  499.  
  500.     /**
  501.      * Delete an existing fortune
  502.      * 
  503.      * @param  int $index 
  504.      * @return void 
  505.      */
  506.     public function delete($index)
  507.     {
  508.         $this->offsetUnset($index);
  509.         return $this;
  510.     }
  511.  
  512.     /**
  513.      * Retrieve random fortune
  514.      * 
  515.      * @return string 
  516.      */
  517.     public function getRandom()
  518.     {
  519.         if (empty($this->_file|| !empty($this->_directory)) {
  520.             $this->_open();
  521.         }
  522.  
  523.         if (is_array($this->_fortunes)) {
  524.             $offset array_rand(array_keys($this->_fortunes));
  525.         else {
  526.             do {
  527.                 $offset array_rand($this->offsets);
  528.             while ($offset < 1);
  529.         }
  530.  
  531.         return $this->offsetGet($offset);
  532.     }
  533.  
  534.     /**
  535.      * Retrieve all fortunes from the current file
  536.      * 
  537.      * @return array 
  538.      */
  539.     public function getAll()
  540.     {
  541.         if (empty($this->_file)) {
  542.             $this->_open();
  543.         }
  544.  
  545.         if (!is_array($this->_fortunes)) {
  546.             $fortunes = array();
  547.             for ($i = 1; $i <= $this->numstr; ++$i{
  548.                 $fortunes[$this->_readFromFile($i);
  549.             }
  550.             $this->_fortunes = $fortunes;
  551.         }
  552.  
  553.         return $this->_fortunes;
  554.     }
  555.  
  556.     /**
  557.      * Save changes
  558.      * 
  559.      * @return void 
  560.      */
  561.     public function save()
  562.     {
  563.         $filename $this->getFile();
  564.         $this->_close();
  565.         $this->setFile($filename);
  566.     }
  567.  
  568.     /**
  569.      * Create a new fortune file from an array of fortunes
  570.      * 
  571.      * @param  array $fortunes 
  572.      * @param  string $file 
  573.      * @return void 
  574.      */
  575.     public function create(array $fortunes$file = null)
  576.     {
  577.         if ((null !== $file&& ($file != $this->getFile())) {
  578.             $this->setFile($file);
  579.         else {
  580.             $file $this->getFile();
  581.         }
  582.         if (file_exists($file)) {
  583.             throw new File_Fortune_Exception('Cannot create fortune file; already exists');
  584.         }
  585.  
  586.         $this->_fortunes = $fortunes;
  587.         $this->_save();
  588.         $this->_close();
  589.     }
  590.  
  591.     /**
  592.      * Open a fortune file
  593.      * 
  594.      * @return void 
  595.      */
  596.     protected function _open()
  597.     {
  598.         if (!empty($this->_file&& empty($this->_directory&& empty($this->_files)) {
  599.             return;
  600.         }
  601.  
  602.         $this->_close();
  603.  
  604.         if ($this->_directory || !empty($this->_files)) {
  605.             // get random file from directory or file list
  606.             $this->_getRandomFile();
  607.         }
  608.  
  609.         if (empty($this->_filename)) {
  610.             throw new File_Fortune_Exception('No file or directory set');
  611.         }
  612.  
  613.         if (!is_readable($this->_filename)) {
  614.             throw new File_Fortune_Exception('Fortune file "' $this->_filename . '" not readable');
  615.         }
  616.  
  617.         if (empty($this->_headerFilename)) {
  618.             $this->setHeaderFile($this->_filename . '.dat');
  619.         }
  620.  
  621.         if (false === ($this->_file = fopen($this->_filename'r'))) {
  622.             throw new File_Fortune_Exception('Unable to read fortune file "' $this->_filename . '"');
  623.         }
  624.  
  625.         $this->_readHeader();
  626.     }
  627.  
  628.     /**
  629.      * Traverse a registered directory to get a list of files
  630.      * 
  631.      * @return void 
  632.      */
  633.     protected function _traverseDirectory()
  634.     {
  635.         if (empty($this->_files)) {
  636.             if (!is_dir($this->_directory|| !is_readable($this->_directory)) {
  637.                 throw new File_Fortune_Exception('Directory "' $this->_directory . '" is invalid or unreadable');
  638.             }
  639.  
  640.             $directory = new DirectoryIterator($this->_directory);
  641.             $files     = array();
  642.             foreach ($directory as $file{
  643.                 if ($file->isDot(|| $file->isDir()) {
  644.                     continue;
  645.                 }
  646.  
  647.                 $filename $file->getFilename();
  648.                 if ('.dat' == substr($filename-4)) {
  649.                     continue;
  650.                 }
  651.  
  652.                 $files[$file->getPathName();
  653.             }
  654.             $this->_files = $files;
  655.         }
  656.     }
  657.  
  658.     /**
  659.      * Randomly select a fortune file from a registered directory
  660.      * 
  661.      * @return void 
  662.      */
  663.     protected function _getRandomFile()
  664.     {
  665.         $this->_traverseDirectory();
  666.         $index array_rand($this->_files);
  667.  
  668.         $this->_fortunes = null;
  669.         $this->_filename = $this->_files[$index];
  670.     }
  671.  
  672.     /**
  673.      * Read a fortune database header file
  674.      * 
  675.      * Reads the header file associated with this fortune database.  The name of
  676.      * the header file is determined by the {@link __construct() constructor}:
  677.      * either it is based on the name of the data file, or supplied by the
  678.      * caller.
  679.      * 
  680.      * If the header file does not exist, this function calls
  681.      * {@link _computeHeader()} automatically, which has the same effect as
  682.      * reading the header from a file.
  683.      * 
  684.      * The header contains the following values, which are stored as attributes
  685.      * of the {@link File_Fortune} object:
  686.      * <ul>
  687.      *     <li>{@link $version}; version number</li>
  688.      *     <li>{@link $numstr}; number of strings (fortunes) in the file</li>
  689.      *     <li>{@link $maxLength}; length of longest string in the file</li>
  690.      *     <li>{@link $minLength}; length of shortest string in the file</li>
  691.      *     <li>{@link $flags}; bit field for flags (see strfile(1) man
  692.      *     page)</li>
  693.      *     <li>{@link $delim}; character that delimits fortunes</li>
  694.      * </ul>
  695.      *
  696.      * {@link $numstr} is available via the count() function (count($fortunes));
  697.      * if you're interested in the others, you'll have to go grubbing through
  698.      * the File_Fortune object, e.g.:
  699.      *
  700.      * <code>
  701.      * $fortune_file = new Fortune('fortunes');
  702.      * $delim        = $fortune_file->delim;
  703.      * $maxLength    = $fortune_file->maxLength;
  704.      * </code>
  705.      *
  706.      * @return void 
  707.      * @throws File_Fortune_Exception
  708.      */
  709.     protected function _readHeader()
  710.     {
  711.         $headerFile $this->getHeaderFile();
  712.         if (!file_exists($headerFile)) {
  713.             return $this->_computeHeader();
  714.         }
  715.       
  716.         if (false === ($file fopen($headerFile'r'))) {
  717.             throw new File_Fortune_Exception('Unable to read header file "' $headerFile '"');
  718.         }
  719.  
  720.         /* 
  721.          * from the strfile(1) man page:
  722.          *       unsigned long str_version;  // version number
  723.          *       unsigned long str_numstr;   // # of strings in the file
  724.          *       unsigned long str_longlen;  // length of longest string
  725.          *       unsigned long str_shortlen; // shortest string length
  726.          *       unsigned long str_flags;    // bit field for flags
  727.          *       char str_delim;             // delimiting character
  728.          * that 'char' is padded out to a full word, so the header is 24 bytes
  729.          */
  730.  
  731.         if (false === ($header fread($file24))) {
  732.             throw new File_Fortune_Exception('Malformed header file "' $headerFile '"');
  733.         }
  734.  
  735.         $values unpack('N5nums/adelim/xxx'$header);
  736.         if (empty($values|| (6 != count($values))) {
  737.             throw new File_Fortune_Exception('Failed to load full header');
  738.         }
  739.  
  740.         $this->version    = $values['nums1'];
  741.         $this->numstr     = $values['nums2'];
  742.         $this->maxLength  = $values['nums3'];
  743.         $this->minLength  = $values['nums4'];
  744.         $this->flags      = $values['nums5'];
  745.         $this->delim      = $values['delim'];
  746.  
  747.         $expectedOffsets $this->numstr + 1;
  748.  
  749.         $amountData = 4 * $expectedOffsets;
  750.  
  751.         if (false === ($data fread($file$amountData))) {
  752.             throw new File_Fortune_Exception('Failed to read offsets for all fortunes');
  753.         }
  754.         $offsets array_merge(unpack('N*'$data));
  755.         if (count($offsets!= $expectedOffsets{
  756.             throw new File_Fortune_Exception('Expected header offsets do not match actual offsets');
  757.         }
  758.         $this->offsets = $offsets;
  759.  
  760.         fclose($file);
  761.     }
  762.  
  763.     /**
  764.      * Compute fortune file header information
  765.      *
  766.      * Reads the contents of the fortune file and computes the header
  767.      * information that would normally be found in a header (.dat) file and read
  768.      * by {@link _readHeader()}.  This is useful if you maintain a file of
  769.      * fortunes by hand and do not have the corresponding data file.
  770.      * 
  771.      * An optional delimiter argument may be passed to this function; if
  772.      * present, that delimiter will be used to separate entries in the fortune
  773.      * file.  If not provided, a percent sign ("%") will be used (this is the
  774.      * standard fortune file delimiter).
  775.      *
  776.      * Additionally, the {@link $noHeader} property will be set to true.
  777.      *
  778.      * <b>NOTE:</b> It is most efficient to use fortune files that have header
  779.      * files. In order to get offsets and fortunes, this method actually must
  780.      * read the entire fortune file, and stores all fortunes in the
  781.      * {@link $_fortunes} array.
  782.      *
  783.      * @param  string $delim Defaults to '%'
  784.      * @return void 
  785.      * @throws File_Fortune_Exception
  786.      */
  787.     protected function _computeHeader($delim '%')
  788.     {
  789.         $filename $this->getFile();
  790.         if (false === ($contents file_get_contents($filename))) {
  791.             throw new File_Fortune_Exception(
  792.                 'Unable to read fortune file (' $filename ') to compute headers'
  793.             );
  794.         }
  795.  
  796.         // Get all fortunes
  797.         // Strip off final delimiter
  798.         $contents substr_replace($contents''strrpos($contents"$delim\n"));
  799.         $fortunes explode("$delim\n"$contents)// explode into array
  800.  
  801.         // Get offsets, min, max, etc.
  802.         $offsets   = array(0);
  803.         $numstr    count($fortunes);
  804.         $curOffset = 0;
  805.         $delimLen  strlen($delim);
  806.         $min       = null;
  807.         $max       = null;
  808.         for ($i = 0; $i $numstr$i++{
  809.             $len        strlen($fortunes[$i]);
  810.             $curOffset += $len $delimLen + 1;
  811.             $offsets[]  $curOffset;
  812.             if (empty($min|| ($min $len)) {
  813.                 $min $len;
  814.             elseif (empty($max|| ($max $len)) {
  815.                 $max $len;
  816.             }
  817.         }
  818.  
  819.         // Set object properties
  820.         $this->version    = 1;
  821.         $this->numstr     = $numstr;
  822.         $this->maxLength  = $max;
  823.         $this->minLength  = $min;
  824.         $this->flags      = 0;
  825.         $this->delim      = $delim;
  826.         $this->offsets    = $offsets;
  827.         $this->_fortunes  = $fortunes;
  828.         $this->noHeader   = true;
  829.     }
  830.  
  831.     /**
  832.      * Close fortune file
  833.      *
  834.      * Closes the fortune file if it's open, and unsets all header-related
  835.      * properties (except {@link $delim}); does nothing otherwise.
  836.      * 
  837.      * @return void 
  838.      */
  839.     protected function _close()
  840.     {
  841.         if (!empty($this->_file)) {
  842.             fclose($this->_file);
  843.             $this->_file = null;
  844.  
  845.             if ($this->_changed{
  846.                 $this->_save();
  847.             }
  848.  
  849.             unset(
  850.                 $this->flags,
  851.                 $this->maxLength,
  852.                 $this->minLength,
  853.                 $this->numstr,
  854.                 $this->offsets,
  855.                 $this->version
  856.             );
  857.  
  858.             $this->delim     = '%';
  859.             $this->_changed  = false;
  860.             $this->_fortunes = null;
  861.         }
  862.     }
  863.  
  864.     /**
  865.      * Read a fortune from the file
  866.      *
  867.      * Reads a fortune directly from the file. If an error occurs, an exception
  868.      * is thrown. Otherwise, on sucess, the fortune is returned.
  869.      *
  870.      * @param int 
  871.      * @return string 
  872.      * @throws File_Fortune_Exception
  873.      */
  874.     protected function _readFromFile($offset
  875.     {
  876.         $start  $this->offsets[$offset - 1];
  877.         $end    $this->offsets[$offset];
  878.         $length $end $start;
  879.  
  880.         // decrement length 2 bytes for most fortunes (to drop trailing "%\n"),
  881.         // and none for the last one (keep trailing newline)
  882.         $delimLength strlen($this->delim| 1;
  883.         $length -= ($offset == $this->numstr? 0 : ($delimLength + 2);
  884.  
  885.         if (-1 == fseek($this->_file$start)) {
  886.             throw new File_Fortune_Exception('Unable to seek to fortune offset');
  887.         }
  888.         $fortune fread($this->_file$length);
  889.  
  890.         return $fortune;
  891.     }
  892.  
  893.     /**
  894.      * Initialize changes
  895.      * 
  896.      * @return void 
  897.      */
  898.     protected function _initChanges()
  899.     {
  900.         $this->_changed = true;
  901.         if (!is_array($this->_fortunes)) {
  902.             $this->getAll();
  903.         }
  904.     }
  905.  
  906.     /**
  907.      * Save changes to disc
  908.      *
  909.      * Changes to fortunes happen in-memory; this method saves them to disc.
  910.      * 
  911.      * @return void 
  912.      */
  913.     protected function _save()
  914.     {
  915.         $this->_initChanges();
  916.  
  917.         if (!empty($this->_file)) {
  918.             fclose($this->_file);
  919.             $this->_file = null;
  920.         }
  921.  
  922.         if (false === ($fh fopen($this->_filename'w'))) {
  923.             throw new File_Fortune_Exception(
  924.                 'Unable to open "' $this->filename . '" to write'
  925.             );
  926.         }
  927.         if (!flock($fhLOCK_EX)) {
  928.             throw new File_Fortune_Exception(
  929.                 'Unable to obtain file lock for writing'
  930.             );
  931.         }
  932.  
  933.         // Write file
  934.         $offsets = array(0)// obtain offsets as we go
  935.         $min     = null;     // For determining shortest fortune
  936.         $max     = null;     // For determining longest fortune
  937.         $numstr  count($this->_fortunes);
  938.         for ($i = 0; $i $numstr; ++$i{
  939.             $fortune $this->_fortunes[$i];
  940.             fwrite($fh$fortune);
  941.             if ($i != ($numstr - 1)) {
  942.                 fwrite($fh"\n" $this->delim . "\n");
  943.             else {
  944.                 fwrite($fh"\n");
  945.             }
  946.             if (false === ($pos ftell($fh))) {
  947.                 throw new File_Fortune_Exception(
  948.                     'Unable to obtain file position while writing fortunes'
  949.                 );
  950.             }
  951.  
  952.             // Determine if this is the min or max length fortune
  953.             $len     strlen($fortune);
  954.             if (empty($min|| ($len $min)) 
  955.                 $min $len;
  956.             
  957.  
  958.             if (empty($max|| ($len $max)) 
  959.                 $max $len;
  960.             }
  961.  
  962.             // Add offset to list
  963.             $offsets[$pos;
  964.         }
  965.         flock($fhLOCK_UN);
  966.         fclose($fh);
  967.  
  968.         // Create header block
  969.         // Set version
  970.         if (empty($this->version)) {
  971.             $this->version = 1;
  972.         else {
  973.             ++$this->version;
  974.         }
  975.  
  976.         // Set other header attributes
  977.         $this->offsets   = $offsets;         // list of offsets
  978.         $this->numstr    = count($this->_fortunes)// number of fortunes
  979.         $this->maxLength = $max;             // longest fortune
  980.         $this->minLength = $min;             // shortest fortune
  981.         if (empty($this->flags)) {
  982.             $this->flags = 0;
  983.         }
  984.  
  985.         // Write header file
  986.         $this->_writeHeader();
  987.     }
  988.  
  989.     /**
  990.      * Write the header file for a fortune file
  991.      *
  992.      * Writes a fortune header file to {@link $headerFilename}, using
  993.      * {@link $version}{@link $numstr}{@link $maxLength},
  994.      * {@link $minLength}{@link $flags}{@link $delim}, and {@link $offsets}.
  995.      *
  996.      * If an error occurs, an exception is thrown.
  997.      *
  998.      * @return void 
  999.      * @throws File_Fortune_Exception
  1000.      */
  1001.     protected function _writeHeader(
  1002.     {
  1003.         $headerFile $this->getHeaderFile();
  1004.         if (empty($headerFile)) {
  1005.             $headerFile $this->getFile('.dat';
  1006.             $this->setHeaderFile($headerFile);
  1007.         }
  1008.  
  1009.         $header pack(
  1010.             'NNNNNaxxx'
  1011.             $this->version,
  1012.             $this->numstr,
  1013.             $this->maxLength,
  1014.             $this->minLength,
  1015.             $this->flags,
  1016.             $this->delim
  1017.         );
  1018.  
  1019.         // Pack offsets
  1020.         $offsetBin '';
  1021.         foreach ($this->offsets as $offset{
  1022.             $offsetBin .= pack('N'$offset);
  1023.         }
  1024.  
  1025.         // Write header file
  1026.         if (false === ($fh fopen($headerFile'w'))) {
  1027.             throw new File_Fortune_Exception(
  1028.                 'Unable to open header file "' $headerFile '" for writing'
  1029.             );
  1030.         }
  1031.         if (!flock($fhLOCK_EX)) {
  1032.             throw new File_Fortune_Exception(
  1033.                 'Unable to obtain lock on header file'
  1034.             );
  1035.         }
  1036.         fwrite($fh$header $offsetBin);
  1037.         flock($fhLOCK_UN);
  1038.         fclose($fh);
  1039.     }
  1040. }
  1041.  
  1042. /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

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