Source for file Fortune.php
Documentation is available at Fortune.php
* File_Fortune: Read and manipulate fortune databases.
* @author Matthew Weier O'Phinney <mweierophinney@gmail.com>, based on
* Fortune.pm, by Greg Ward
* @copyright 2005 - 2007, Matthew Weier O'Phinney; Fortune.pm v0.2(c) 1999, Greg Ward
* @version CVS: $Id: Fortune.php 173 2007-07-24 13:15:24Z matthew $
* @license New BSD {@link http://www.opensource.org/licenses/bsd-license.php}
/** File_Fortune_Exception */
require_once 'File/Fortune/Exception.php';
* File_Fortune: Interface to fortune cookie databases
* The <em>fortune</em> program is a small but important part of *nix
* culture, and this package aims to provide support for its "fortune cookie"
* databases to PHP programmers.
* @version @release-version@
* @tutorial File_Fortune/File_Fortune.cls
class File_Fortune implements Iterator , Countable , ArrayAccess
* Character that delimits fortunes (defaults to '%')
* Length of longest string in the fortune file
* Length of shortest string in the fortune file
* Version number for fortune file
* Flag - has an operation changed the file?
* Current offset (used by iterator)
* Directory containing fortune files
* Fortune file filehandle
* Name of fortune file to use
* Array of fortune files found in $_directory
* Array of fortunes; unset by default.
* Name of fortune header file (.dat file)
* Flag indicating whether or not a header file is present. Defaults to
* Number of strings (fortunes) in the file
* Array of file offsets (fortune indices)
* Optionally pass a filename or directory name to set the fortune file or
* directory, and, if passing a fortune file name, optionally pass the name
* @param string|array$file
* @param string $headerFile
public function __construct($file = null , $headerFile = null )
$this->setFile($file, $headerFile);
* Return current fortune in iterator
* @throws File_Fortune_Exception
if (empty ($this->_file)) {
* Return current iterator key
* @throws File_Fortune_Exception
if (empty ($this->_file)) {
* Retrieve next element in iterator
* @throws File_Fortune_Exception
if (empty ($this->_file)) {
* Rewind iterator to first element
* @throws File_Fortune_Exception
if (empty ($this->_file)) {
* Current element is valid (iterator)
* @throws File_Fortune_Exception
if (empty ($this->_file)) {
* Count of fortunes in current active file, or of all fortunes in all files
* @throws File_Fortune_Exception
if (!empty ($this->_file)) {
foreach ($this->_files as $file) {
if (empty ($this->_file)) {
* Does fortune exist at the given offset?
* @throws File_Fortune_Exception
* Retrieve fortune by offset
* @throws File_Fortune_Exception
* Set a fortune at a given offset
* @throws File_Fortune_Exception
* Delete a fortune at a given offset
* @throws File_Fortune_Exception
* Setting a file overwrites any directory set as well.
* @param string $headerFile
public function setFile($file, $headerFile = null )
* Retrieve current fortune file name
* @param string $headerFile
* Retrieve current header file name
* Set one or more files to search for fortunes
* @throws File_Fortune_Exception
$this->_files = array ((string) $spec);
foreach ($argv as $file) {
$this->_files[] = (string) $file;
* Return list of fortune files in use
* Returns a list of fortune files in use, either as registered via
* {@link setFiles()} or as determined by traversing the directory set via
* {@link setDirectory()}.
* Set directory from which to randomly select a fortune file
* @param string $directory
* Retrieve current directory of fortune files
* @throws File_Fortune_Exception
public function add($fortune)
* Update an existing fortune
* @throws File_Fortune_Exception
public function update($index, $fortune)
* Delete an existing fortune
* @throws File_Fortune_Exception
public function delete($index)
* Retrieve random fortune
* Retrieve all fortunes from the current file or set of files
if (!empty ($this->_file)) {
foreach ($this->_files as $file) {
* @throws File_Fortune_Exception
* Create a new fortune file from an array of fortunes
* @throws File_Fortune_Exception
public function create(array $fortunes, $file = null )
if ((null !== $file) && ($file != $this->getFile ())) {
$file = $this->getFile ();
* @throws File_Fortune_Exception
protected function _open()
// get random file from directory or file list
if (empty ($this->_headerFilename )) {
* Traverse a registered directory to get a list of files
* @throws File_Fortune_Exception
$directory = new DirectoryIterator ($this->_directory);
foreach ($directory as $file) {
if ($file->isDot () || $file->isDir ()) {
$filename = $file->getFilename ();
if ('.dat' == substr($filename, -4 )) {
$files[] = $file->getPathName ();
* Randomly select a fortune file from a registered directory
* Read a fortune database header file
* Reads the header file associated with this fortune database. The name of
* the header file is determined by the {@link __construct() constructor}:
* either it is based on the name of the data file, or supplied by the
* If the header file does not exist, this function calls
* {@link _computeHeader()} automatically, which has the same effect as
* reading the header from a file.
* The header contains the following values, which are stored as attributes
* of the {@link File_Fortune} object:
* <li>{@link $version}; version number</li>
* <li>{@link $numstr}; number of strings (fortunes) in the file</li>
* <li>{@link $maxLength}; length of longest string in the file</li>
* <li>{@link $minLength}; length of shortest string in the file</li>
* <li>{@link $flags}; bit field for flags (see strfile(1) man
* <li>{@link $delim}; character that delimits fortunes</li>
* {@link $numstr} is available via the count() function (count($fortunes));
* if you're interested in the others, you'll have to go grubbing through
* the File_Fortune object, e.g.:
* $fortune_file = new Fortune('fortunes');
* $delim = $fortune_file->delim;
* $maxLength = $fortune_file->maxLength;
* @throws File_Fortune_Exception
if (false === ($file = fopen($headerFile, 'r'))) {
* from the strfile(1) man page:
* unsigned long str_version; // version number
* unsigned long str_numstr; // # of strings in the file
* unsigned long str_longlen; // length of longest string
* unsigned long str_shortlen; // shortest string length
* unsigned long str_flags; // bit field for flags
* char str_delim; // delimiting character
* that 'char' is padded out to a full word, so the header is 24 bytes
if (false === ($header = fread($file, 24 ))) {
$values = unpack('N5nums/adelim/xxx', $header);
if (empty ($values) || (6 != count($values))) {
$this->numstr = $values['nums2'];
$this->flags = $values['nums5'];
$this->delim = $values['delim'];
$expectedOffsets = $this->numstr + 1;
$amountData = 4 * $expectedOffsets;
if (false === ($data = fread($file, $amountData))) {
if (count($offsets) != $expectedOffsets) {
* Compute fortune file header information
* Reads the contents of the fortune file and computes the header
* information that would normally be found in a header (.dat) file and read
* by {@link _readHeader()}. This is useful if you maintain a file of
* fortunes by hand and do not have the corresponding data file.
* An optional delimiter argument may be passed to this function; if
* present, that delimiter will be used to separate entries in the fortune
* file. If not provided, a percent sign ("%") will be used (this is the
* standard fortune file delimiter).
* Additionally, the {@link $noHeader} property will be set to true.
* <b>NOTE:</b> It is most efficient to use fortune files that have header
* files. In order to get offsets and fortunes, this method actually must
* read the entire fortune file, and stores all fortunes in the
* {@link $_fortunes} array.
* @param string $delim Defaults to '%'
* @throws File_Fortune_Exception
'Unable to read fortune file (' . $filename . ') to compute headers'
// Strip off final delimiter
$fortunes = explode(" $delim\n" , $contents); // explode into array
// Get offsets, min, max, etc.
$numstr = count($fortunes);
for ($i = 0; $i < $numstr; $i++ ) {
$curOffset += $len + $delimLen + 1;
if (empty ($min) || ($min > $len)) {
} elseif (empty ($max) || ($max < $len)) {
* Closes the fortune file if it's open, and unsets all header-related
* properties (except {@link $delim}); does nothing otherwise.
if (!empty ($this->_file)) {
* Read a fortune from the file
* Reads a fortune directly from the file. If an error occurs, an exception
* is thrown. Otherwise, on sucess, the fortune is returned.
* @throws File_Fortune_Exception
$start = $this->offsets[$offset - 1 ];
// decrement length 2 bytes for most fortunes (to drop trailing "%\n"),
// and none for the last one (keep trailing newline)
$length -= ($offset == $this->numstr) ? 0 : ($delimLength + 2 );
* Changes to fortunes happen in-memory; this method saves them to disc.
* @throws File_Fortune_Exception
protected function _save()
if (!empty ($this->_file)) {
'Unable to open "' . $this->filename . '" to write'
if (!flock($fh, LOCK_EX )) {
'Unable to obtain file lock for writing'
$offsets = array (0 ); // obtain offsets as we go
$min = null; // For determining shortest fortune
$max = null; // For determining longest fortune
for ($i = 0; $i < $numstr; ++ $i) {
if ($i != ($numstr - 1 )) {
if (false === ($pos = ftell($fh))) {
'Unable to obtain file position while writing fortunes'
// Determine if this is the min or max length fortune
if (empty ($min) || ($len < $min)) {
if (empty ($max) || ($len > $max)) {
// Set other header attributes
$this->offsets = $offsets; // list of offsets
if (empty ($this->flags )) {
* Write the header file for a fortune file
* Writes a fortune header file to {@link $headerFilename}, using
* {@link $version}, {@link $numstr}, {@link $maxLength},
* {@link $minLength}, {@link $flags}, {@link $delim}, and {@link $offsets}.
* If an error occurs, an exception is thrown.
* @throws File_Fortune_Exception
if (empty ($headerFile)) {
$headerFile = $this->getFile() . '.dat';
foreach ($this->offsets as $offset) {
$offsetBin .= pack('N', $offset);
if (false === ($fh = fopen($headerFile, 'w'))) {
'Unable to open header file "' . $headerFile . '" for writing'
if (!flock($fh, LOCK_EX )) {
'Unable to obtain lock on header file'
fwrite($fh, $header . $offsetBin);
* Retrieve by offset in file
if (empty ($this->_file)) {
&& isset ($this->offsets[$offset - 1 ]))
* Retrieve a single fortune by offset
* @throws File_Fortune_Exception
protected function _getOne($offset)
* Retrieve all fortunes from the current active file
if (empty ($this->_file)) {
for ($i = 1; $i <= $this->numstr; ++ $i) {
/* 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.
|