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 136 2007-07-08 16:04:22Z 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.0RC1';
  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.         if (empty($this->_file)) {
  340.             $this->_open();
  341.         }
  342.  
  343.         if (is_array($this->_fortunes&& isset($this->_fortunes[$offset - 1])) {
  344.             return true;
  345.         elseif (!is_array($this->_fortunes
  346.             && isset($this->offsets[$offset])
  347.             && isset($this->offsets[$offset - 1])) 
  348.         {
  349.             return true;
  350.         }
  351.  
  352.         return false;
  353.     }
  354.  
  355.     /**
  356.      * Retrieve fortune by offset
  357.      * 
  358.      * @param  int $offset 
  359.      * @return string 
  360.      * @throws File_Fortune_Exception
  361.      */
  362.      public function offsetGet($offset)
  363.     {
  364.         if (!empty($this->_directory|| !empty($this->_files)) {
  365.             throw new File_Fortune_Exception('Array access not allowed when directory or multiple files set');
  366.         }
  367.  
  368.         if ($this->offsetExists($offset)) {
  369.             if (is_array($this->_fortunes)) {
  370.                 return $this->_fortunes[$offset - 1];
  371.             }
  372.  
  373.             return $this->_readFromFile($offset);
  374.         }
  375.  
  376.         throw new File_Fortune_Exception('Requested fortune index does not exist');
  377.     }
  378.  
  379.     /**
  380.      * Set a fortune at a given offset
  381.      * 
  382.      * @param  int $offset 
  383.      * @param  string $value 
  384.      * @return void 
  385.      * @throws File_Fortune_Exception
  386.      */
  387.      public function offsetSet($offset$value)
  388.     {
  389.         if (!empty($this->_directory|| !empty($this->_files)) {
  390.             throw new File_Fortune_Exception('Array access not allowed when directory or multiple files set');
  391.         }
  392.  
  393.         $this->_initChanges();
  394.         if (!$this->offsetExists($offset)) {
  395.             throw new File_Fortune_Exception('Cannot modify fortune; offset does not exist');
  396.         }
  397.         $this->_fortunes[$offset - 1$value;
  398.     }
  399.  
  400.     /**
  401.      * Delete a fortune at a given offset
  402.      * 
  403.      * @param  int $offset 
  404.      * @return void 
  405.      * @throws File_Fortune_Exception
  406.      */
  407.      public function offsetUnset($offset)
  408.     {
  409.         if (!empty($this->_directory|| !empty($this->_files)) {
  410.             throw new File_Fortune_Exception('Array access not allowed when directory or multiple files set');
  411.         }
  412.  
  413.         if (!$this->offsetExists($offset)) {
  414.             throw new File_Fortune_Exception('Cannot delete fortune; offset does not exist');
  415.         }
  416.         $this->_initChanges();
  417.         unset($this->_fortunes[$offset - 1]);
  418.         $this->_fortunes = array_merge(array()$this->_fortunes);
  419.         $this->numstr = count($this->_fortunes);
  420.     }
  421.  
  422.     /**
  423.      * Set fortune file
  424.      *
  425.      * Setting a file overwrites any directory set as well.
  426.      * 
  427.      * @param  string $file 
  428.      * @param  string $headerFile 
  429.      * @return File_Fortune 
  430.      */
  431.     public function setFile($file$headerFile = null)
  432.     {
  433.         if ($file != $this->_filename{
  434.             $this->_close();
  435.         }
  436.         if (!empty($this->_directory)) {
  437.             $this->_directory = null;
  438.         }
  439.  
  440.         $this->_filename = (string) $file;
  441.  
  442.         $this->setHeaderFile($headerFile);
  443.         return $this;
  444.     }
  445.  
  446.     /**
  447.      * Retrieve current fortune file name
  448.      * 
  449.      * @return string 
  450.      */
  451.     public function getFile()
  452.     {
  453.         return $this->_filename;
  454.     }
  455.  
  456.     /**
  457.      * Set header file name
  458.      * 
  459.      * @param  string $headerFile 
  460.      * @return File_Fortune 
  461.      */
  462.     public function setHeaderFile($headerFile)
  463.     {
  464.         $this->_headerFile = (string) $headerFile;
  465.         return $this;
  466.     }
  467.  
  468.     /**
  469.      * Retrieve current header file name
  470.      * 
  471.      * @return string 
  472.      */
  473.     public function getHeaderFile()
  474.     {
  475.         return $this->_headerFile;
  476.     }
  477.  
  478.     /**
  479.      * Set one or more files to search for fortunes
  480.      * 
  481.      * @return File_Fortune 
  482.      * @throws File_Fortune_Exception
  483.      */
  484.     public function setFiles()
  485.     {
  486.         if (0 == func_num_args()) {
  487.             throw new File_Fortune_Exception('setFiles() expects at least one argument');
  488.         }
  489.  
  490.         $this->_close();
  491.         $this->_directory = null;
  492.         $this->_filename  = null;
  493.         $this->_fortunes  = null;
  494.  
  495.         $argv func_get_args();
  496.         $spec array_shift($argv);
  497.         if (is_array($spec)) {
  498.             $this->_files = $spec;
  499.             return $this;
  500.         }
  501.         $this->_files = array((string) $spec);
  502.         foreach ($argv as $file{
  503.             $this->_files[= (string) $file;
  504.         }
  505.  
  506.         return $this;
  507.     }
  508.  
  509.     /**
  510.      * Return list of fortune files in use
  511.      *
  512.      * Returns a list of fortune files in use, either as registered via
  513.      * {@link setFiles()} or as determined by traversing the directory set via
  514.      * {@link setDirectory()}.
  515.      * 
  516.      * @return array 
  517.      */
  518.     public function getFiles()
  519.     {
  520.         if ((null === $this->_files&& !empty($this->_directory)) {
  521.             $this->_traverseDirectory();
  522.         }
  523.         return $this->_files;
  524.     }
  525.  
  526.     /**
  527.      * Set directory from which to randomly select a fortune file
  528.      * 
  529.      * @param  string $directory 
  530.      * @return File_Fortune 
  531.      */
  532.     public function setDirectory($directory)
  533.     {
  534.         $this->_close();
  535.  
  536.         $this->_directory = $directory;
  537.         $this->_files     = null;
  538.         $this->_fortunes  = null;
  539.         $this->_filename  = null;
  540.  
  541.         return $this;
  542.     }
  543.  
  544.     /**
  545.      * Retrieve current directory of fortune files
  546.      * 
  547.      * @return string 
  548.      */
  549.     public function getDirectory()
  550.     {
  551.         return $this->_directory;
  552.     }
  553.  
  554.     /**
  555.      * Add a new fortune
  556.      * 
  557.      * @param  string $fortune 
  558.      * @return File_Fortune 
  559.      * @throws File_Fortune_Exception
  560.      */
  561.     public function add($fortune)
  562.     {
  563.         if (!empty($this->_directory|| !empty($this->_files)) {
  564.             throw new File_Fortune_Exception('Cannot add fortune when directory or multiple files specified');
  565.         }
  566.  
  567.         $this->_initChanges();
  568.         $this->_fortunes[$fortune;
  569.         $this->numstr = count($this->_fortunes);
  570.         return $this;
  571.     }
  572.  
  573.     /**
  574.      * Update an existing fortune
  575.      * 
  576.      * @param  int $index 
  577.      * @param  string $fortune 
  578.      * @return void 
  579.      * @throws File_Fortune_Exception
  580.      */
  581.     public function update($index$fortune)
  582.     {
  583.         if (!empty($this->_directory|| !empty($this->_files)) {
  584.             throw new File_Fortune_Exception('Cannot update fortune when directory or multiple files specified');
  585.         }
  586.  
  587.         $this->offsetSet($index$fortune);
  588.         return $this;
  589.     }
  590.  
  591.     /**
  592.      * Delete an existing fortune
  593.      * 
  594.      * @param  int $index 
  595.      * @return void 
  596.      * @throws File_Fortune_Exception
  597.      */
  598.     public function delete($index)
  599.     {
  600.         if (!empty($this->_directory|| !empty($this->_files)) {
  601.             throw new File_Fortune_Exception('Cannot delete fortune when directory or multiple files specified');
  602.         }
  603.  
  604.         $this->offsetUnset($index);
  605.         return $this;
  606.     }
  607.  
  608.     /**
  609.      * Retrieve random fortune
  610.      * 
  611.      * @return string 
  612.      */
  613.     public function getRandom()
  614.     {
  615.         if (empty($this->_file|| !empty($this->_directory)) {
  616.             $this->_open();
  617.         }
  618.  
  619.         if (is_array($this->_fortunes)) {
  620.             $offset array_rand(array_keys($this->_fortunes));
  621.         else {
  622.             do {
  623.                 $offset array_rand($this->offsets);
  624.             while ($offset < 1);
  625.         }
  626.  
  627.         return $this->offsetGet($offset);
  628.     }
  629.  
  630.     /**
  631.      * Retrieve all fortunes from the current file or set of files
  632.      * 
  633.      * @return array 
  634.      */
  635.     public function getAll()
  636.     {
  637.         if (!empty($this->_directory)) {
  638.             $this->_traverseDirectory();
  639.         }
  640.  
  641.         if (!empty($this->_files)) {
  642.             if (!is_array($this->_fortunes)) {
  643.                 $fortunes = array();
  644.                 if (!empty($this->_file)) {
  645.                     fclose($this->_file);
  646.                     $this->_file = null;
  647.                 }
  648.                 foreach ($this->_files as $file{
  649.                     $this->_filename = $file;
  650.                     $fortunes array_merge($fortunes$this->_getAll());
  651.                     fclose($this->_file);
  652.                     $this->_file = null;
  653.                 }
  654.                 $this->_fortunes = $fortunes;
  655.             }
  656.         else {
  657.             if (!is_array($this->_fortunes)) {
  658.                 $this->_fortunes = $this->_getAll();
  659.             }
  660.         }
  661.  
  662.         return $this->_fortunes;
  663.     }
  664.  
  665.     /**
  666.      * Save changes
  667.      * 
  668.      * @return void 
  669.      * @throws File_Fortune_Exception
  670.      */
  671.     public function save()
  672.     {
  673.         if (!empty($this->_directory|| !empty($this->_files)) {
  674.             throw new File_Fortune_Exception('Cannot save changes when directory or multiple files set');
  675.         }
  676.  
  677.         $filename $this->getFile();
  678.         $this->_close();
  679.         $this->setFile($filename);
  680.     }
  681.  
  682.     /**
  683.      * Create a new fortune file from an array of fortunes
  684.      * 
  685.      * @param  array $fortunes 
  686.      * @param  string $file 
  687.      * @return void 
  688.      * @throws File_Fortune_Exception
  689.      */
  690.     public function create(array $fortunes$file = null)
  691.     {
  692.         if ((null !== $file&& ($file != $this->getFile())) {
  693.             $this->setFile($file);
  694.         else {
  695.             $file $this->getFile();
  696.         }
  697.         if (null === $file{
  698.             throw new File_Fortune_Exception('Cannot create fortune file; no file specified');
  699.         }
  700.         if (file_exists($file)) {
  701.             throw new File_Fortune_Exception('Cannot create fortune file; already exists');
  702.         }
  703.  
  704.         $this->_fortunes = $fortunes;
  705.         $this->_save();
  706.         $this->_close();
  707.     }
  708.  
  709.     /**
  710.      * Open a fortune file
  711.      * 
  712.      * @return void 
  713.      * @throws File_Fortune_Exception
  714.      */
  715.     protected function _open()
  716.     {
  717.         if (!empty($this->_file&& empty($this->_directory&& empty($this->_files)) {
  718.             return;
  719.         }
  720.  
  721.         $this->_close();
  722.  
  723.         if ($this->_directory || !empty($this->_files)) {
  724.             // get random file from directory or file list
  725.             $this->_getRandomFile();
  726.         }
  727.  
  728.         if (empty($this->_filename)) {
  729.             throw new File_Fortune_Exception('No file or directory set');
  730.         }
  731.  
  732.         if (!is_readable($this->_filename)) {
  733.             throw new File_Fortune_Exception('Fortune file "' $this->_filename . '" not readable');
  734.         }
  735.  
  736.         if (empty($this->_headerFilename)) {
  737.             $this->setHeaderFile($this->_filename . '.dat');
  738.         }
  739.  
  740.         if (false === ($this->_file = fopen($this->_filename'r'))) {
  741.             throw new File_Fortune_Exception('Unable to read fortune file "' $this->_filename . '"');
  742.         }
  743.  
  744.         $this->_readHeader();
  745.     }
  746.  
  747.     /**
  748.      * Traverse a registered directory to get a list of files
  749.      * 
  750.      * @return void 
  751.      * @throws File_Fortune_Exception
  752.      */
  753.     protected function _traverseDirectory()
  754.     {
  755.         if (empty($this->_files)) {
  756.             if (!is_dir($this->_directory|| !is_readable($this->_directory)) {
  757.                 throw new File_Fortune_Exception('Directory "' $this->_directory . '" is invalid or unreadable');
  758.             }
  759.  
  760.             $directory = new DirectoryIterator($this->_directory);
  761.             $files     = array();
  762.             foreach ($directory as $file{
  763.                 if ($file->isDot(|| $file->isDir()) {
  764.                     continue;
  765.                 }
  766.  
  767.                 $filename $file->getFilename();
  768.                 if ('.dat' == substr($filename-4)) {
  769.                     continue;
  770.                 }
  771.  
  772.                 $files[$file->getPathName();
  773.             }
  774.             $this->_files = $files;
  775.         }
  776.     }
  777.  
  778.     /**
  779.      * Randomly select a fortune file from a registered directory
  780.      * 
  781.      * @return void 
  782.      */
  783.     protected function _getRandomFile()
  784.     {
  785.         $this->_traverseDirectory();
  786.         $index array_rand($this->_files);
  787.  
  788.         $this->_fortunes = null;
  789.         $this->_filename = $this->_files[$index];
  790.     }
  791.  
  792.     /**
  793.      * Read a fortune database header file
  794.      * 
  795.      * Reads the header file associated with this fortune database.  The name of
  796.      * the header file is determined by the {@link __construct() constructor}:
  797.      * either it is based on the name of the data file, or supplied by the
  798.      * caller.
  799.      * 
  800.      * If the header file does not exist, this function calls
  801.      * {@link _computeHeader()} automatically, which has the same effect as
  802.      * reading the header from a file.
  803.      * 
  804.      * The header contains the following values, which are stored as attributes
  805.      * of the {@link File_Fortune} object:
  806.      * <ul>
  807.      *     <li>{@link $version}; version number</li>
  808.      *     <li>{@link $numstr}; number of strings (fortunes) in the file</li>
  809.      *     <li>{@link $maxLength}; length of longest string in the file</li>
  810.      *     <li>{@link $minLength}; length of shortest string in the file</li>
  811.      *     <li>{@link $flags}; bit field for flags (see strfile(1) man
  812.      *     page)</li>
  813.      *     <li>{@link $delim}; character that delimits fortunes</li>
  814.      * </ul>
  815.      *
  816.      * {@link $numstr} is available via the count() function (count($fortunes));
  817.      * if you're interested in the others, you'll have to go grubbing through
  818.      * the File_Fortune object, e.g.:
  819.      *
  820.      * <code>
  821.      * $fortune_file = new Fortune('fortunes');
  822.      * $delim        = $fortune_file->delim;
  823.      * $maxLength    = $fortune_file->maxLength;
  824.      * </code>
  825.      *
  826.      * @return void 
  827.      * @throws File_Fortune_Exception
  828.      */
  829.     protected function _readHeader()
  830.     {
  831.         $headerFile $this->getHeaderFile();
  832.         if (!file_exists($headerFile)) {
  833.             return $this->_computeHeader();
  834.         }
  835.       
  836.         if (false === ($file fopen($headerFile'r'))) {
  837.             throw new File_Fortune_Exception('Unable to read header file "' $headerFile '"');
  838.         }
  839.  
  840.         /* 
  841.          * from the strfile(1) man page:
  842.          *       unsigned long str_version;  // version number
  843.          *       unsigned long str_numstr;   // # of strings in the file
  844.          *       unsigned long str_longlen;  // length of longest string
  845.          *       unsigned long str_shortlen; // shortest string length
  846.          *       unsigned long str_flags;    // bit field for flags
  847.          *       char str_delim;             // delimiting character
  848.          * that 'char' is padded out to a full word, so the header is 24 bytes
  849.          */
  850.  
  851.         if (false === ($header fread($file24))) {
  852.             throw new File_Fortune_Exception('Malformed header file "' $headerFile '"');
  853.         }
  854.  
  855.         $values unpack('N5nums/adelim/xxx'$header);
  856.         if (empty($values|| (6 != count($values))) {
  857.             throw new File_Fortune_Exception('Failed to load full header');
  858.         }
  859.  
  860.         $this->version    = $values['nums1'];
  861.         $this->numstr     = $values['nums2'];
  862.         $this->maxLength  = $values['nums3'];
  863.         $this->minLength  = $values['nums4'];
  864.         $this->flags      = $values['nums5'];
  865.         $this->delim      = $values['delim'];
  866.  
  867.         $expectedOffsets $this->numstr + 1;
  868.  
  869.         $amountData = 4 * $expectedOffsets;
  870.  
  871.         if (false === ($data fread($file$amountData))) {
  872.             throw new File_Fortune_Exception('Failed to read offsets for all fortunes');
  873.         }
  874.         $offsets array_merge(unpack('N*'$data));
  875.         if (count($offsets!= $expectedOffsets{
  876.             throw new File_Fortune_Exception('Expected header offsets do not match actual offsets');
  877.         }
  878.         $this->offsets = $offsets;
  879.  
  880.         fclose($file);
  881.     }
  882.  
  883.     /**
  884.      * Compute fortune file header information
  885.      *
  886.      * Reads the contents of the fortune file and computes the header
  887.      * information that would normally be found in a header (.dat) file and read
  888.      * by {@link _readHeader()}.  This is useful if you maintain a file of
  889.      * fortunes by hand and do not have the corresponding data file.
  890.      * 
  891.      * An optional delimiter argument may be passed to this function; if
  892.      * present, that delimiter will be used to separate entries in the fortune
  893.      * file.  If not provided, a percent sign ("%") will be used (this is the
  894.      * standard fortune file delimiter).
  895.      *
  896.      * Additionally, the {@link $noHeader} property will be set to true.
  897.      *
  898.      * <b>NOTE:</b> It is most efficient to use fortune files that have header
  899.      * files. In order to get offsets and fortunes, this method actually must
  900.      * read the entire fortune file, and stores all fortunes in the
  901.      * {@link $_fortunes} array.
  902.      *
  903.      * @param  string $delim Defaults to '%'
  904.      * @return void 
  905.      * @throws File_Fortune_Exception
  906.      */
  907.     protected function _computeHeader($delim '%')
  908.     {
  909.         $filename $this->getFile();
  910.         if (false === ($contents file_get_contents($filename))) {
  911.             throw new File_Fortune_Exception(
  912.                 'Unable to read fortune file (' $filename ') to compute headers'
  913.             );
  914.         }
  915.  
  916.         // Get all fortunes
  917.         // Strip off final delimiter
  918.         $contents substr_replace($contents''strrpos($contents"$delim\n"));
  919.         $fortunes explode("$delim\n"$contents)// explode into array
  920.  
  921.         // Get offsets, min, max, etc.
  922.         $offsets   = array(0);
  923.         $numstr    count($fortunes);
  924.         $curOffset = 0;
  925.         $delimLen  strlen($delim);
  926.         $min       = null;
  927.         $max       = null;
  928.         for ($i = 0; $i $numstr$i++{
  929.             $len        strlen($fortunes[$i]);
  930.             $curOffset += $len $delimLen + 1;
  931.             $offsets[]  $curOffset;
  932.             if (empty($min|| ($min $len)) {
  933.                 $min $len;
  934.             elseif (empty($max|| ($max $len)) {
  935.                 $max $len;
  936.             }
  937.         }
  938.  
  939.         // Set object properties
  940.         $this->version    = 1;
  941.         $this->numstr     = $numstr;
  942.         $this->maxLength  = $max;
  943.         $this->minLength  = $min;
  944.         $this->flags      = 0;
  945.         $this->delim      = $delim;
  946.         $this->offsets    = $offsets;
  947.         $this->_fortunes  = $fortunes;
  948.         $this->noHeader   = true;
  949.     }
  950.  
  951.     /**
  952.      * Close fortune file
  953.      *
  954.      * Closes the fortune file if it's open, and unsets all header-related
  955.      * properties (except {@link $delim}); does nothing otherwise.
  956.      * 
  957.      * @return void 
  958.      */
  959.     protected function _close()
  960.     {
  961.         if (!empty($this->_file)) {
  962.             fclose($this->_file);
  963.             $this->_file = null;
  964.  
  965.             if ($this->_changed{
  966.                 $this->_save();
  967.             }
  968.  
  969.             unset(
  970.                 $this->flags,
  971.                 $this->maxLength,
  972.                 $this->minLength,
  973.                 $this->numstr,
  974.                 $this->offsets,
  975.                 $this->version
  976.             );
  977.  
  978.             $this->delim     = '%';
  979.             $this->_changed  = false;
  980.             $this->_fortunes = null;
  981.         }
  982.     }
  983.  
  984.     /**
  985.      * Read a fortune from the file
  986.      *
  987.      * Reads a fortune directly from the file. If an error occurs, an exception
  988.      * is thrown. Otherwise, on sucess, the fortune is returned.
  989.      *
  990.      * @param int 
  991.      * @return string 
  992.      * @throws File_Fortune_Exception
  993.      */
  994.     protected function _readFromFile($offset
  995.     {
  996.         $start  $this->offsets[$offset - 1];
  997.         $end    $this->offsets[$offset];
  998.         $length $end $start;
  999.  
  1000.         // decrement length 2 bytes for most fortunes (to drop trailing "%\n"),
  1001.         // and none for the last one (keep trailing newline)
  1002.         $delimLength strlen($this->delim| 1;
  1003.         $length -= ($offset == $this->numstr? 0 : ($delimLength + 2);
  1004.  
  1005.         if (-1 == fseek($this->_file$start)) {
  1006.             throw new File_Fortune_Exception('Unable to seek to fortune offset');
  1007.         }
  1008.         $fortune fread($this->_file$length);
  1009.  
  1010.         return $fortune;
  1011.     }
  1012.  
  1013.     /**
  1014.      * Initialize changes
  1015.      * 
  1016.      * @return void 
  1017.      */
  1018.     protected function _initChanges()
  1019.     {
  1020.         $this->_changed = true;
  1021.         if (!is_array($this->_fortunes)) {
  1022.             $this->getAll();
  1023.         }
  1024.     }
  1025.  
  1026.     /**
  1027.      * Save changes to disc
  1028.      *
  1029.      * Changes to fortunes happen in-memory; this method saves them to disc.
  1030.      * 
  1031.      * @return void 
  1032.      * @throws File_Fortune_Exception
  1033.      */
  1034.     protected function _save()
  1035.     {
  1036.         $this->_initChanges();
  1037.  
  1038.         if (!empty($this->_file)) {
  1039.             fclose($this->_file);
  1040.             $this->_file = null;
  1041.         }
  1042.  
  1043.         if (false === ($fh fopen($this->_filename'w'))) {
  1044.             throw new File_Fortune_Exception(
  1045.                 'Unable to open "' $this->filename . '" to write'
  1046.             );
  1047.         }
  1048.         if (!flock($fhLOCK_EX)) {
  1049.             throw new File_Fortune_Exception(
  1050.                 'Unable to obtain file lock for writing'
  1051.             );
  1052.         }
  1053.  
  1054.         // Write file
  1055.         $offsets = array(0)// obtain offsets as we go
  1056.         $min     = null;     // For determining shortest fortune
  1057.         $max     = null;     // For determining longest fortune
  1058.         $numstr  count($this->_fortunes);
  1059.         for ($i = 0; $i $numstr; ++$i{
  1060.             $fortune $this->_fortunes[$i];
  1061.             fwrite($fh$fortune);
  1062.             if ($i != ($numstr - 1)) {
  1063.                 fwrite($fh"\n" $this->delim . "\n");
  1064.             else {
  1065.                 fwrite($fh"\n");
  1066.             }
  1067.             if (false === ($pos ftell($fh))) {
  1068.                 throw new File_Fortune_Exception(
  1069.                     'Unable to obtain file position while writing fortunes'
  1070.                 );
  1071.             }
  1072.  
  1073.             // Determine if this is the min or max length fortune
  1074.             $len     strlen($fortune);
  1075.             if (empty($min|| ($len $min)) 
  1076.                 $min $len;
  1077.             
  1078.  
  1079.             if (empty($max|| ($len $max)) 
  1080.                 $max $len;
  1081.             }
  1082.  
  1083.             // Add offset to list
  1084.             $offsets[$pos;
  1085.         }
  1086.         flock($fhLOCK_UN);
  1087.         fclose($fh);
  1088.  
  1089.         // Create header block
  1090.         // Set version
  1091.         if (empty($this->version)) {
  1092.             $this->version = 1;
  1093.         else {
  1094.             ++$this->version;
  1095.         }
  1096.  
  1097.         // Set other header attributes
  1098.         $this->offsets   = $offsets;         // list of offsets
  1099.         $this->numstr    = count($this->_fortunes)// number of fortunes
  1100.         $this->maxLength = $max;             // longest fortune
  1101.         $this->minLength = $min;             // shortest fortune
  1102.         if (empty($this->flags)) {
  1103.             $this->flags = 0;
  1104.         }
  1105.  
  1106.         // Write header file
  1107.         $this->_writeHeader();
  1108.     }
  1109.  
  1110.     /**
  1111.      * Write the header file for a fortune file
  1112.      *
  1113.      * Writes a fortune header file to {@link $headerFilename}, using
  1114.      * {@link $version}{@link $numstr}{@link $maxLength},
  1115.      * {@link $minLength}{@link $flags}{@link $delim}, and {@link $offsets}.
  1116.      *
  1117.      * If an error occurs, an exception is thrown.
  1118.      *
  1119.      * @return void 
  1120.      * @throws File_Fortune_Exception
  1121.      */
  1122.     protected function _writeHeader(
  1123.     {
  1124.         $headerFile $this->getHeaderFile();
  1125.         if (empty($headerFile)) {
  1126.             $headerFile $this->getFile('.dat';
  1127.             $this->setHeaderFile($headerFile);
  1128.         }
  1129.  
  1130.         $header pack(
  1131.             'NNNNNaxxx'
  1132.             $this->version,
  1133.             $this->numstr,
  1134.             $this->maxLength,
  1135.             $this->minLength,
  1136.             $this->flags,
  1137.             $this->delim
  1138.         );
  1139.  
  1140.         // Pack offsets
  1141.         $offsetBin '';
  1142.         foreach ($this->offsets as $offset{
  1143.             $offsetBin .= pack('N'$offset);
  1144.         }
  1145.  
  1146.         // Write header file
  1147.         if (false === ($fh fopen($headerFile'w'))) {
  1148.             throw new File_Fortune_Exception(
  1149.                 'Unable to open header file "' $headerFile '" for writing'
  1150.             );
  1151.         }
  1152.         if (!flock($fhLOCK_EX)) {
  1153.             throw new File_Fortune_Exception(
  1154.                 'Unable to obtain lock on header file'
  1155.             );
  1156.         }
  1157.         fwrite($fh$header $offsetBin);
  1158.         flock($fhLOCK_UN);
  1159.         fclose($fh);
  1160.     }
  1161.  
  1162.     /**
  1163.      * Retrieve all fortunes from the current active file
  1164.      * 
  1165.      * @return void 
  1166.      */
  1167.     protected function _getAll()
  1168.     {
  1169.         if (empty($this->_file)) {
  1170.             $this->_open();
  1171.         }
  1172.  
  1173.         $fortunes = array();
  1174.         for ($i = 1; $i <= $this->numstr; ++$i{
  1175.             $fortunes[$this->_readFromFile($i);
  1176.         }
  1177.  
  1178.         return $fortunes;
  1179.     }
  1180. }
  1181.  
  1182. /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

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