Location: PHPKode > scripts > Lightweight Club Calendar > lc-calendar-0.9.4/drivers/note/file.class.php
<?php
/**
* File based NoteDriver for LCC
*
* PHP Version 4, 5
*
* @author <hide@address.com>
* @copyright Copyright (c) 2006, Benedikt Hallinger
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, 51 Franklin St, Fifth Floor,
* Boston, MA  02110-1301  USA
*/

/**
* A Driver that is capaple of storing and retrieving notes from flat files
*
* It is meant for small installations, because several race conditions exists where data could
* possibly be overriden, if too much people are editing the same note at the same time.
* Consider putting the storage place into a linux tmpfs or use another driver.
* However, it implements some kind of index file to speed up the note creation tasks etc a bit
*
* Possible configuration:
*  'datapath'    Directory where the notes data is stored, full path with trailing slash.
*                Default is the subdirectory 'filestore' of LCC_Core's 'datadir'
*/
class LCC_NoteDriver_File extends LCC_NoteDriver
{
	/**
	* Default config of the driver
	*
	* This gets overwritten by the core calling setConfig()
	* The default value of 'datapath' is set
	*
	* @var array
	*/
	var $config = array(
			'datapath' => ''  //where to store the files; default set by init()
		);

	/**
	* Used by the core to fetch all ids of notes for a specific event $event_id
	*
	*
	* @param mixed $event_id     Id of the event whose notes should be loaded
	* @return array|false        Array with Ids of the notes
	*/
	function getNotes($event_id)
	{
		$this->_checkConfig();
		
		// Determine, what notes belong to the event
		$index = $this->_getIndex();
		$notes_for_this_event = array();
		foreach ($index as $line) {
			$row = split('\|', trim($line));
			if (is_numeric($row[0]) && $row[1] == $event_id) {
				array_push($notes_for_this_event, $row[0]);
			}
		}
		
		return $notes_for_this_event;
	}
		
	/**
	* Fetch data of a specific note
	*
	* This returns the data for a note as associative array
	* containing field => value.
	* The method is also used to determine if a note exists
	*
	* @param mixed $id   ID of the note
	* @return array|false
	*/
	function getData($id)
	{
		$this->_checkConfig();
		// Construct the filename of the notes file
		$data = false; // Assume note doesn't exist by default.
		if ($this->_searchIndexForID($id)) {
			$filename = $this->config['datapath'].'note_'.$id;
			if (!is_readable($filename)) {
				die('<b>LCC_NoteDriver_File ERROR:</b> Note file "'.$filename.'" is not readable!');
			} else {
				$file_conent = file($filename);
				if (is_array($file_conent)) {
					// Okay, parse the content and return the data
					// Syntax is KEY=Value
					$data = array();
					foreach ($file_conent as $line) {
						if (preg_match("/^(.+)=(.+)$/", $line, $match)) {
							// There are storage problems with newlines in our flat files,
							// here we decode what _writeNoteData() encodes
							$data[$match[1]] = str_replace('-§-', PHP_EOL, $match[2]);
						}
					}
				} else {
					die('<b>LCC_NoteDriver_File ERROR:</b> Could not contents of note file "'.$filename.'"');
				}
			}
		}

		return $data;
	}

	/**
	* This method is used to write data into the backend. $data is a associative array
	*
	* similar to the return value of getData().
	* If $id is empty (= false, null or '') or not present in the backend, a new note must be created.
	* Otherwise the present data must be updated.
	* Return the ID of the note written or false, depending if storage succeedet
	*
	* @param int $id   ID of the note
	* @param int $id   Data that should be written
	* @return int|false
	*/
	function storeData($id, $data)
	{
		$this->_checkConfig();
		$return = false;
		
		if (is_array($data)) {
		
			// Check if the note already exists
			// no: determine new id and create file
			if (!$this->_searchIndexForID($id)) {
				// find the next free ID
				$id     = 0;   // where to start from
				$max_id = $id; // init max_id
				$contents = $this->_getIndex();
				if (empty($contents)) {
					// no index file exists so far, start with ID=1
					$id++;
				} else {
					// search for highest index number
					foreach ($contents as $line_raw) {
						$line = split('\|',$line_raw);
						$index_id = $line[0];
						if ($max_id < $index_id) $max_id = $index_id;
					}
					$id = $max_id +1;
				}
			}
			
			// write new (or updated) data
			// and update index
			$result = $this->_writeNoteData($id, $data);
			if ($result !== false) {
				$this->_updateIndexFile($id, $data['event_id']);
				$return = $id;
			}
		}
		
		return $return;
	}
	
	/**
	* This is used to delete this specific note in the backend.
	*
	* @return true|false
	*/
	function deleteNote($id)
	{
		 $filename = $this->config['datapath'].'note_'.$id;
		
		 $this->_checkConfig();
		 $this->_updateIndexFile($id, 'delete'); // Delete from index
		 if (file_exists($filename)) {
		 	return unlink($filename);
		 } else {
			return true;
		 }
	}
	
	/**
	* This, optional, method is called at driver loading time if it exists
	*
	* In this case, we need this feature, because we want to use LCC's
	* default data directory to store our data into.
	* This cant be set in a custom constructor since we have no access to the
	* core object there.
	*/
	function init() {
		/*
		* set 'datapath' to datadir of LCC_Core
		*/
		$datadir = $this->lcc_core->getConfig('data_dir'); // fetch core config
		
		// Correct the path if its not a path - a trailing / is expected
		if (substr($datadir,-1,1) != '/') {
			$datadir .= '/';
		}
		
		// set our default value
		$this->setConfig(array('datapath' => $datadir.'filestore/'));
	}

	
	
	
	/*******************************************************************
	* Here come some driver internal methods that make our life easier *
	*******************************************************************/
	
	/**
	* Verify that the drivers config is loaded and sane
	*
	* @return true      Return true or dies
	* @access private
	*/
	function _checkConfig()
	{
		if(!is_array($this->config) ||
		   !isset($this->config['datapath'])) {
			die('<b>LCC_NoteDriver_File ERROR:</b> Configuration of driver not found or not valid');
		}

		// Correct the path if its not a path - a trailing / is expected
		if (substr($this->config['datapath'],-1,1) != '/') {
			$this->config['datapath'] .= '/';
		}
		
		// check that filepath is accessible
		if (!is_writable($this->config['datapath'])) {
			die('<b>LCC_NoteDriver_File ERROR:</b> Configured datapath "'.$this->config['datapath'].'" is not writable!');
		}
		
		return true;
	}

	/**
	* This actually fetches the data from the index file
	*
	* @todo maybe implement a cache
	* @return array      Content of the index file (raw data)
	* @access private
	*/
	function _getIndex()
	{
		$this->_checkConfig();
		$contents = array();  // index file is not here, so we assume there are no notes. just return an empty array
		$indexfile = $this->config['datapath'].'note_index';
		if (file_exists($indexfile)) {
			if (!is_readable($indexfile)) {
				die('<b>LCC_NoteDriver_File ERROR:</b> Could not open indexfile: '.$indexfile);
			} else {
				$contents = file($indexfile);
				if (!is_array($contents)) {
					die('<b>LCC_NoteDriver_File ERROR:</b> Could not get contents of indexfile (but is was here and readable?): '.$indexfile);
				}
			}
		}
		
		return $contents;
	}
	
	/**
	* Update the index file entry
	*
	* @param mixed $id        ID of the note to be updated
	* @param mixed $event_id  ID of the event this note belongs to
	* @access private
	*/
	function _updateIndexFile($id, $event_id)
	{
		$this->_checkConfig();
		
		if ($event_id === 'delete') {
			$delete_note = true;
		} else {
			$delete_note = false;
		}
		
		$indexfile = $this->config['datapath'].'note_index';
		if (!is_writable($indexfile) && file_exists($indexfile)) {
			die('<b>LCC_NoteDriver_File ERROR:</b> Could not open indexfile in write mode: '.$indexfile);
		} else {
			if ($this->_searchIndexForID($id)) {
				// Entry exists, update
	
				// Maybe we have a nice possibility where we can just float over the contents
				// and only replace the line neccessary
				$contents = $this->_getIndex();
	
				$i_handle = fopen($indexfile, 'w');
				flock($i_handle, LOCK_EX);
				foreach ($contents as $line) {
					if (preg_match('/^'.$id.'\|/', $line)) {
						// this is the id we want to update
						// if we want to delete this note from index, just dont write it
						if (!$delete_note) {
							fwrite($i_handle, $id.'|'.$event_id.PHP_EOL);
						}
					} else {
						fwrite($i_handle, $line);
					}
				}
				flock($i_handle, LOCK_UN);
				fclose($i_handle);
			} else {
				// Entry doesn't exist, add
				$i_handle = fopen($indexfile, 'a');
				if ($i_handle == false) {
					die('<b>LCC_NoteDriver_File ERROR:</b> Unable to update indexfile '.$indexfile);
				}
				flock($i_handle, LOCK_EX);
				fwrite($i_handle, $id.'|'.$event_id.PHP_EOL);
				flock($i_handle, LOCK_UN);
				fclose($i_handle);
			}
		}
	}
	
	/**
	* Is there a note with id $id in the index file?
	*
	* @return boolean
	*/
	function _searchIndexForID($id) {
		$contents = $this->_getIndex();
		$return = false;  // Index file is empty, so we assume no notes
		if ($contents) {
			foreach ($contents as $line_raw){
				$line = split('\|',$line_raw);
				$index_id = $line[0];
				if ($id == $index_id) {
					$return = true;
					break;
				}
			}
		}

		return $return;
	}
	
	/**
	* This writes data to a note file
	*
	* If the file exists, it will be updatet, if not it will be created.
	*
	* @param $id
	* @param $data   associative data array (field => value)
	* @return boolean
	* @access private
	*/
	function _writeNoteData($id, $data) {
		$this->_checkConfig();
		
		// Construct the filename of the note file
		$filename = $this->config['datapath'].'note_'.$id;
		
		if (file_exists($filename) && !is_writable($filename)) {
			die('<b>LCC_NoteDriver_File ERROR:</b> Note file "'.$filename.'" is not writable!');
		}
		
		// Write to note file
		$handle = fopen($filename, 'w');
		if ($handle == false) {
			die('<b>LCC_NoteDriver_File ERROR:</b> Unable to create new note file '.$filename);
		}
		
		flock($handle, LOCK_EX);
		// there is a problem with newlines in our flat file, we must encode them
		foreach ($data as $field => $value) {
			fwrite($handle, $field.'='.str_replace(PHP_EOL, '-§-', $value).PHP_EOL);
		}
		flock($handle, LOCK_UN);
		fclose($handle);
		
		return true;
	}
}
?>
Return current item: Lightweight Club Calendar