1: <?php
2: /**
3: * This will store the formstate in a pre-specified directory on the file system.
4: * This offers significant speed advantage over PHP SESSION because EACH form state
5: * is saved in its own file, and only the form state that is needed for loading will
6: * be accessed (as opposed to with session, ALL the form states are loaded into memory
7: * every time).
8: *
9: * The downside is that because it doesn't utilize PHP's session management subsystem,
10: * this class must take care of its own garbage collection/deleting of old/outdated
11: * formstate files.
12: *
13: * Because the index is randomy generated and MD5-hashed, there is no benefit from
14: * encrypting it -- therefore, the QForm encryption preferences are ignored when using
15: * QFileFormStateHandler.
16: *
17: * This formstate handler is compatible with asynchronous ajax calls.
18: */
19: class QFileFormStateHandler extends QBaseClass {
20: /**
21: * The PATH where the FormState files should be saved
22: *
23: * @var string StatePath
24: */
25: public static $StatePath = __FILE_FORM_STATE_HANDLER_PATH__;
26:
27: /**
28: * The filename prefix to be used by all FormState files
29: *
30: * @var string FileNamePrefix
31: */
32: public static $FileNamePrefix = 'qformstate_';
33:
34: /**
35: * The interval of hits before the garbage collection should kick in to delete
36: * old FormState files, or 0 if it should never be run. The higher the number,
37: * the less often it runs (better aggregated-average performance, but requires more
38: * hard drive space). The lower the number, the more often it runs (slower aggregated-average
39: * performance, but requires less hard drive space).
40: *
41: * @var integer GarbageCollectInterval
42: */
43: public static $GarbageCollectInterval = 200;
44:
45: /**
46: * The minimum age (in days) a formstate file has to be in order to be considered old enough
47: * to be garbage collected. So if set to "1.5", then all formstate files older than 1.5 days
48: * will be deleted when the GC interval is kicked off.
49: *
50: * Obviously, if the GC Interval is set to 0, then this GC Days Old value will be never used.
51: *
52: * @var integer GarbageCollectDaysOld
53: */
54: public static $GarbageCollectDaysOld = 2;
55:
56: /**
57: * If PHP SESSION is enabled, then this method will delete all formstate files specifically
58: * for this SESSION user (and no one else). This can be used in lieu of or in addition to the
59: * standard interval-based garbage collection mechanism.
60: *
61: * Also, for standard web applications with logins, it might be a good idea to call
62: * this method whenever the user logs out.
63: */
64: public static function DeleteFormStateForSession() {
65: // Figure Out Session Id (if applicable)
66: $strSessionId = session_id();
67:
68: $strPrefix = self::$FileNamePrefix . $strSessionId;
69:
70: // Go through all the files
71: if (strlen($strSessionId)) {
72: $objDirectory = dir(self::$StatePath);
73: while (($strFile = $objDirectory->read()) !== false) {
74: $intPosition = strpos($strFile, $strPrefix);
75: if (($intPosition !== false) && ($intPosition == 0))
76: unlink(sprintf('%s/%s', self::$StatePath, $strFile));
77: }
78: }
79: }
80:
81: /**
82: * This will delete all the formstate files that are older than $GarbageCollectDaysOld
83: * days old.
84: */
85: public static function GarbageCollect() {
86: // Go through all the files
87: $objDirectory = dir(self::$StatePath);
88: while (($strFile = $objDirectory->read()) !== false) {
89: if (!count(self::$FileNamePrefix))
90: $intPosition = 0;
91: else
92: $intPosition = strpos($strFile, self::$FileNamePrefix);
93: if (($intPosition !== false) && ($intPosition == 0)) {
94: $strFile = sprintf('%s/%s', self::$StatePath, $strFile);
95: $intTimeInterval = time() - (60 * 60 * 24 * self::$GarbageCollectDaysOld);
96: $intModifiedTime = filemtime($strFile);
97:
98: if ($intModifiedTime < $intTimeInterval)
99: unlink($strFile);
100: }
101: }
102: }
103:
104: public static function Save($strFormState, $blnBackButtonFlag) {
105: // First see if we need to perform garbage collection
106: if (self::$GarbageCollectInterval > 0) {
107: // This is a crude interval-tester, but it works
108: if (rand(1, self::$GarbageCollectInterval) == 1)
109: self::GarbageCollect();
110: }
111:
112: // Compress (if available)
113: if (function_exists('gzcompress'))
114: $strFormState = gzcompress($strFormState, 9);
115:
116: // Figure Out Session Id (if applicable)
117: $strSessionId = session_id();
118:
119: if (!empty($_POST['Qform__FormState']) && QApplication::$RequestMode == QRequestMode::Ajax) {
120: $strPageId = $_POST['Qform__FormState']; // reuse old page id
121: } else {
122: // Calculate a new unique Page Id
123: $strPageId = md5(microtime());
124: }
125:
126: // Figure Out FilePath
127: $strFilePath = sprintf('%s/%s%s_%s',
128: self::$StatePath,
129: self::$FileNamePrefix,
130: $strSessionId,
131: $strPageId);
132:
133: // Save THIS formstate to the file system
134: // NOTE: if gzcompress is used, we are saving the *BINARY* data stream of the compressed formstate
135: // In theory, this SHOULD work. But if there is a webserver/os/php version that doesn't like
136: // binary session streams, you can first base64_encode before saving to session (see note below).
137: file_put_contents($strFilePath, $strFormState);
138:
139: // Return the Page Id
140: // Because of the MD5-random nature of the Page ID, there is no need/reason to encrypt it
141: return $strPageId;
142: }
143:
144: public static function Load($strPostDataState) {
145: // Pull Out strPageId
146: $strPageId = $strPostDataState;
147:
148: // Figure Out Session Id (if applicable)
149: $strSessionId = session_id();
150:
151: // Figure Out FilePath
152: $strFilePath = sprintf('%s/%s%s_%s',
153: self::$StatePath,
154: self::$FileNamePrefix,
155: $strSessionId,
156: $strPageId);
157:
158: if (file_exists($strFilePath)) {
159: // Pull FormState from file system
160: // NOTE: if gzcompress is used, we are restoring the *BINARY* data stream of the compressed formstate
161: // In theory, this SHOULD work. But if there is a webserver/os/php version that doesn't like
162: // binary session streams, you can first base64_decode before restoring from session (see note above).
163: $strSerializedForm = file_get_contents($strFilePath);
164:
165: // Uncompress (if available)
166: if (function_exists('gzcompress'))
167: $strSerializedForm = gzuncompress($strSerializedForm);
168:
169: return $strSerializedForm;
170: } else
171: return null;
172: }
173: }