1: <?php
2: /*
3: * Copyright (C) 2013 Bryan Nielsen - All Rights Reserved
4: *
5: * Author: Bryan Nielsen (bnielsen1965@gmail.com)
6: *
7: *
8: * This file is part of cryptUser.
9: * cryptUser is free software: you can redistribute it and/or modify
10: * it under the terms of the GNU General Public License as published by
11: * the Free Software Foundation, either version 3 of the License, or
12: * (at your option) any later version.
13: *
14: * cryptUser is distributed in the hope that it will be useful,
15: * but WITHOUT ANY WARRANTY; without even the implied warranty of
16: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17: * GNU General Public License for more details.
18: *
19: * You should have received a copy of the GNU General Public License
20: * along with cryptUser. If not, see <http://www.gnu.org/licenses/>.
21: */
22:
23: namespace BSN\CryptUser;
24: use Exception;
25:
26: require_once 'CryptDataSource.php';
27:
28: /**
29: * A JSON file based data source object for the CryptUsers.
30: *
31: * @author Bryan Nielsen, bnielsen1965@gmail.com
32: * @copyright Copyright 2013, Bryan Nielsen
33: */
34: class CryptJSONSource implements CryptDataSource {
35: private $filename;
36: private $lockedFilePointer; // pointer to the currently open locked file
37: private $lockedFilename; // filename for the currently open locked file
38:
39:
40: /**
41: * Constructor to set up the data source
42: *
43: */
44: public function __construct($filename) {
45: // reset locked file values
46: $this->lockedFilePointer = NULL;
47: $this->lockedFilename = '';
48:
49: $this->filename = $filename;
50:
51: // read file to test file locking
52: $this->readJSONFile();
53: }
54:
55:
56: /**
57: * Get source type
58: *
59: * @return string The data source type.
60: */
61: public function getSourceType() {
62: return 'JSON';
63: }
64:
65:
66: /**
67: * Get array of users that match a given name.
68: * @param string $username The username to search for in the data source.
69: * @return array|boolean An array of arrays containing user elements to create a user
70: * or FALSE if not found.
71: */
72: public function getUserByName($username) {
73: // read the JSON file
74: $users = $this->readJSONFile($this->filename);
75:
76: if ($users) {
77: foreach ($users as $user) {
78: if ($user['username'] == $username) return $user;
79: }
80: }
81:
82: // failed to find user
83: return FALSE;
84: }
85:
86:
87: /**
88: * Get a list of usernames
89: * @return array An array of strings containing the usernames from the data source.
90: */
91: public function getUsernames() {
92: $users = $this->readJSONFile($this->filename);
93:
94: if ($users) {
95: $usernames = array();
96:
97: foreach ($users as $user) {
98: $usernames[] = $user['username'];
99: }
100:
101: return $usernames;
102: }
103: else return FALSE;
104: }
105:
106:
107: /**
108: * Save the provided user details in the data source.
109: * @param array $user An array of user elements to be saved.
110: * @return boolean Returns TRUE on success and FALSE on failure.
111: */
112: public function saveUser($user) {
113: // read the JSON file
114: $users = $this->readJSONFile($this->filename);
115:
116: // determine if user exists
117: if (($ui = $this->searchUsersForUser($users, $user['username'])) !== FALSE) {
118: // found user index, update user
119: $users[$ui] = $user;
120: }
121: else {
122: // user not found, add to users
123: $users[] = $user;
124: }
125:
126: return $this->writeJSONFile($users, $this->filename);
127: }
128:
129:
130: /**
131: * Delete the specified user from the data source.
132: * @param string $username The username of the user to delete.
133: * @return boolean Returns TRUE on success and FALSE on failure.
134: */
135: public function deleteUser($username) {
136: // read the JSON file
137: $users = $this->readJSONFile($this->filename);
138:
139: // find the index to the specified user
140: $userIndex = $this->searchUsersForUser($users, $username);
141:
142: if ($userIndex !== FALSE) {
143: // remove the user from the list
144: unset($users[$userIndex]);
145:
146: // save the list
147: return $this->writeJSONFile($users, $this->filename);
148: }
149: else {
150: return FALSE;
151: }
152: }
153:
154:
155: /**
156: * Search provided users array for the specified user.
157: * @param array $users An array of user rows to search.
158: * @return integer|boolean The array index for the user name or FALSE if not found.
159: */
160: private function searchUsersForUser($users, $username) {
161: if ($users) {
162: foreach ($users as $ui => $user) {
163: if ($user['username'] == $username) return $ui;
164: }
165: }
166:
167: return FALSE;
168: }
169:
170:
171:
172:
173: /**
174: * Read a JSON formatted file using the file locking mechanism and return as an
175: * associative array
176: *
177: * @param string $filename Optional filename of the JSON file to write. If not
178: * specified then the objects filename setting will be used.
179: * @return array | boolean An associative array or FALSE on failure.
180: */
181: public function readJSONFile($filename = NULL) {
182: // use this source filename if not provided
183: if (empty($filename)) $filename = $this->filename;
184:
185: // read the JSON file
186: $stringJSON = $this->lockedRead($filename);
187: $arrayJSON = json_decode($stringJSON, TRUE);
188: return $arrayJSON;
189: }
190:
191:
192: /**
193: * Write a JSON formatted file using the file locking mechanism. The provided
194: * data array or object element will be used for the file contents.
195: *
196: * @param array | object $data An associative array or object to convert to JSON.
197: * @param string $filename Optional filename of the JSON file to write. If not
198: * specified then the objects filename setting will be used.
199: * @return boolean TRUE on success, FALSE on failure.
200: */
201: public function writeJSONFile($data, $filename = NULL) {
202: // use this source filename if not provided
203: if (empty($filename)) $filename = $this->filename;
204:
205: // save the JSON data to the file
206: return $this->lockedWrite($this->filename, json_encode($data));
207: }
208:
209:
210: /**
211: * Open and lock a file for both read and write operations.
212: *
213: * @param filename Filename of file to open and lock.
214: * @param createIfNotExist Optional boolean to specify if the file should be created if it does not exist.
215: * @return boolean Open status.
216: * @throws Exception If cannot get lock on file for any reason.
217: */
218: public function lockFile($filename, $createIfNotExist = TRUE) {
219: // check to see if opening a new file
220: if ($filename != $this->lockedFilename || empty($this->lockedFilePointer)) {
221: // create file if it does not exist
222: if ($createIfNotExist && !file_exists($filename)) touch($filename);
223:
224: // if file pointer is already open then close
225: if ($this->lockedFilePointer) {
226: flock($this->lockedFilePointer, LOCK_UN);
227: fclose($this->lockedFilePointer);
228: $this->lockedFilePointer = NULL;
229: $this->lockedFilename = '';
230: }
231:
232: // open the file
233: $this->lockedFilePointer = fopen($filename, 'r+');
234:
235: // try to get exclusive lock on the file
236: if ($this->lockedFilePointer && flock($this->lockedFilePointer, LOCK_EX)) {
237: $this->lockedFilename = $filename;
238: }
239: else {
240: // lock failed
241: $this->lockedFilePointer = NULL;
242: $this->lockedFilename = '';
243:
244: throw new Exception("Failed to get the lock on file!");
245: }
246: }
247: }
248:
249:
250: /**
251: * Read file with lock.
252: *
253: * @param filename Filename of file to read.
254: * @param createIfNotExist Optional boolean to specify if the file should be created if it does not exist.
255: * @return string or boolean Returns file contents in a string or FALSE on failure.
256: * @throws Exception If for any reason the file is not locked.
257: */
258: public function lockedRead($filename, $createIfNotExist = TRUE) {
259: // make sure file is opened and locked
260: $this->lockFile($filename, $createIfNotExist);
261:
262: // if we have the file locked then read
263: if( $this->lockedFilePointer ) {
264: // make sure we are at beginning of file
265: fseek($this->lockedFilePointer, 0);
266:
267: // read lines from the file
268: $buffer = '';
269: while ($line = fgets($this->lockedFilePointer)) {
270: $buffer .= $line;
271: }
272:
273: // return buffer
274: return $buffer;
275: }
276: else {
277: throw new Exception("Failed to get the lock on file!");
278: }
279: }
280:
281:
282: /**
283: * Write buffer to file with lock
284: *
285: * @param string $filename The filename to write.
286: * @param string $buffer The buffer to write to the file.
287: * @param boolean $createIfNotExist Optional, specifies if the file should be
288: * created if it does not already exist.
289: * @return boolean FALSE if write fails
290: * @throws Exception If for any reason the file is not locked.
291: */
292: public function lockedWrite($filename, $buffer, $createIfNotExist = TRUE) {
293: // make sure file is opened and locked
294: $this->lockFile($filename, $createIfNotExist);
295:
296: // if we have the file locked then write
297: if( $this->lockedFilePointer ) {
298: // make sure we are at beginning of file
299: fseek($this->lockedFilePointer, 0);
300:
301: // erase file using truncate
302: ftruncate($this->lockedFilePointer, 0);
303:
304: // write buffer
305: fwrite($this->lockedFilePointer, $buffer);
306: fflush($this->lockedFilePointer);
307:
308: // return success
309: return TRUE;
310: }
311: else {
312: throw new Exception("Failed to get the lock on file!");
313: }
314: }
315:
316:
317: /**
318: * The magic __wakeup() function is used to clean up the file pointers when
319: * the object is unserialized.
320: */
321: public function __wakeup() {
322: // if file pointer is already open then close
323: if ($this->lockedFilePointer) {
324: $this->lockedFilePointer = NULL;
325: }
326: }
327: }
328:
329: