├── .gitignore
├── _config.php
└── code
├── UpdateForSyncroTask.php
├── controllers
└── RemoteNodeAdmin.php
├── dataobjects
├── RemoteSyncroNode.php
└── SyncroDelete.php
├── extensions
├── SyncroSiteConfig.php
└── SyncroableExtension.php
├── jobs
└── SyncrotronJob.php
├── lib
└── Uuid.php
├── services
├── Syncroable.php
├── SyncrotronPermissions.php
└── SyncrotronService.php
├── tests
└── TestSyncrotron.php
└── thirdparty
├── _manifest_exclude
└── klogger
├── .gitignore
├── README.markdown
├── example
└── example.php
└── src
└── KLogger.php
/.gitignore:
--------------------------------------------------------------------------------
1 | sync-logs/
2 |
--------------------------------------------------------------------------------
/_config.php:
--------------------------------------------------------------------------------
1 | Title." ... ";
19 | $obj->write();
20 | echo $obj->ContentID . "
\n";
21 | }
22 | }
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/code/controllers/RemoteNodeAdmin.php:
--------------------------------------------------------------------------------
1 | 'Varchar(128)',
11 | 'NodeURL' => 'Varchar(128)',
12 | 'APIToken' => 'Varchar(128)',
13 | 'LastSync' => 'SS_Datetime',
14 | );
15 |
16 | public static $summary_fields = array(
17 | 'NodeURL',
18 | 'LastSync',
19 | );
20 |
21 | public static $searchable_fields = array(
22 | 'NodeURL',
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/code/dataobjects/SyncroDelete.php:
--------------------------------------------------------------------------------
1 | 'Varchar(128)',
13 | 'Type' => 'Varchar(128)',
14 | 'Deleted' => 'SS_Datetime',
15 | );
16 |
17 | public static function record_delete($object) {
18 | $delete = new SyncroDelete();
19 | $delete->ContentID = $object->ContentID;
20 | $delete->Type = $object->ClassName;
21 | $delete->Deleted = gmdate('Y-m-d H:i:s');
22 | $delete->write();
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/code/extensions/SyncroSiteConfig.php:
--------------------------------------------------------------------------------
1 | array(
12 | 'SystemID' => 'Varchar(128)'
13 | )
14 | );
15 | }
16 |
17 | public function onBeforeWrite() {
18 | if (!$this->owner->SystemID) {
19 | $this->owner->SystemID = Uuid::get();
20 | }
21 | }
22 |
23 | public function getSyncroIdentifier() {
24 | if (!$this->owner->SystemID) {
25 | // write now to make sure ID is set
26 | $this->owner->write();
27 | }
28 | return $this->owner->SystemID;
29 | }
30 |
31 | public function updateCMSFields(FieldSet &$fields) {
32 | $fields->addFieldToTab('Root.Syncro', new ReadonlyField('SystemID', _t('Syncro.SYSTEM_ID', 'System Identifier')));
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/code/extensions/SyncroableExtension.php:
--------------------------------------------------------------------------------
1 | array(
21 | 'MasterNode' => 'Varchar(128)',
22 | 'ContentID' => 'Varchar(128)',
23 | 'CreatedUTC' => 'SS_Datetime', // create time on master node
24 | 'LastEditedUTC' => 'SS_Datetime', // utc last edited time on master node
25 | 'UpdatedUTC' => 'SS_Datetime', // utc last edited time on any node
26 | 'OriginalID' => 'Int',
27 | )
28 | );
29 | }
30 |
31 | public function onBeforeWrite() {
32 | $config = SiteConfig::current_site_config();
33 | if (!$this->owner->MasterNode) {
34 | $this->owner->MasterNode = $config->getSyncroIdentifier();
35 | }
36 |
37 | if (!$this->owner->ContentID) {
38 | $this->owner->ContentID = Uuid::get();
39 | }
40 |
41 | $nowUTC = gmdate('Y-m-d H:i:s');
42 | $this->owner->UpdatedUTC = $nowUTC;
43 |
44 | // if we're updating on the master node, change the lasteditedUTC and created UTC if needbe
45 | if ($this->owner->MasterNode == $config->getSyncroIdentifier()) {
46 | $this->owner->LastEditedUTC = $nowUTC;
47 | if (!$this->owner->CreatedUTC) {
48 | $this->owner->CreatedUTC = $nowUTC;
49 | }
50 | }
51 | }
52 |
53 | public function onAfterWrite() {
54 | if (!$this->owner->OriginalID) {
55 | $this->owner->OriginalID = $this->owner->ID;
56 | $this->owner->write();
57 | }
58 | }
59 |
60 | public function updateFrontendFields(FieldSet $fields) {
61 | $fields->removeByName('MasterNode');
62 | $fields->removeByName('ContentID');
63 | $fields->removeByName('OriginalID');
64 | $fields->removeByName('LastEditedUTC');
65 | }
66 |
67 | public function onAfterDelete() {
68 | parent::onAfterDelete();
69 | if ($this->owner->MasterNode == SiteConfig::current_site_config()->SystemID) {
70 | SyncroDelete::record_delete($this->owner);
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/code/jobs/SyncrotronJob.php:
--------------------------------------------------------------------------------
1 | totalSteps = 1;
13 | }
14 |
15 | public function getTitle() {
16 | return 'Syncronise data from remote nodes';
17 | }
18 |
19 | public function process() {
20 | singleton('SyncrotronService')->getUpdates();
21 | singleton('QueuedJobService')->queueJob(new SyncrotronJob(), date('Y-m-d H:i:s', time() + self::SYNC_TIME));
22 |
23 | $this->currentStep = $this->totalSteps;
24 | $this->isComplete = true;
25 |
26 |
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/code/lib/Uuid.php:
--------------------------------------------------------------------------------
1 | urand = @fopen ( '/dev/urandom', 'rb' );
11 | }
12 |
13 | /**
14 | * @brief Generates a Universally Unique IDentifier, version 4.
15 | *
16 | * This function generates a truly random UUID. The built in CakePHP String::uuid() function
17 | * is not cryptographically secure. You should uses this function instead.
18 | *
19 | * @see http://tools.ietf.org/html/rfc4122#section-4.4
20 | * @see http://en.wikipedia.org/wiki/UUID
21 | * @return string A UUID, made up of 32 hex digits and 4 hyphens.
22 | */
23 | function get() {
24 |
25 | $pr_bits = false;
26 | if (is_a ( $this, 'uuid' )) {
27 | if (is_resource ( $this->urand )) {
28 | $pr_bits .= @fread ( $this->urand, 16 );
29 | }
30 | }
31 | if (! $pr_bits) {
32 | $fp = @fopen ( '/dev/urandom', 'rb' );
33 | if ($fp !== false) {
34 | $pr_bits .= @fread ( $fp, 16 );
35 | @fclose ( $fp );
36 | } else {
37 | // If /dev/urandom isn't available (eg: in non-unix systems), use mt_rand().
38 | $pr_bits = "";
39 | for($cnt = 0; $cnt < 16; $cnt ++) {
40 | $pr_bits .= chr ( mt_rand ( 0, 255 ) );
41 | }
42 | }
43 | }
44 | $time_low = bin2hex ( substr ( $pr_bits, 0, 4 ) );
45 | $time_mid = bin2hex ( substr ( $pr_bits, 4, 2 ) );
46 | $time_hi_and_version = bin2hex ( substr ( $pr_bits, 6, 2 ) );
47 | $clock_seq_hi_and_reserved = bin2hex ( substr ( $pr_bits, 8, 2 ) );
48 | $node = bin2hex ( substr ( $pr_bits, 10, 6 ) );
49 |
50 | /**
51 | * Set the four most significant bits (bits 12 through 15) of the
52 | * time_hi_and_version field to the 4-bit version number from
53 | * Section 4.1.3.
54 | * @see http://tools.ietf.org/html/rfc4122#section-4.1.3
55 | */
56 | $time_hi_and_version = hexdec ( $time_hi_and_version );
57 | $time_hi_and_version = $time_hi_and_version >> 4;
58 | $time_hi_and_version = $time_hi_and_version | 0x4000;
59 |
60 | /**
61 | * Set the two most significant bits (bits 6 and 7) of the
62 | * clock_seq_hi_and_reserved to zero and one, respectively.
63 | */
64 | $clock_seq_hi_and_reserved = hexdec ( $clock_seq_hi_and_reserved );
65 | $clock_seq_hi_and_reserved = $clock_seq_hi_and_reserved >> 2;
66 | $clock_seq_hi_and_reserved = $clock_seq_hi_and_reserved | 0x8000;
67 |
68 | return sprintf ( '%08s-%04s-%04x-%04x-%012s', $time_low, $time_mid, $time_hi_and_version, $clock_seq_hi_and_reserved, $node );
69 | }
70 |
71 | }
--------------------------------------------------------------------------------
/code/services/Syncroable.php:
--------------------------------------------------------------------------------
1 | dataService = singleton('DataService');
52 | $this->log = new KLogger(SYNCROTRON_LOGDIR, KLogger::INFO);
53 | }
54 |
55 | /**
56 | * The list of methods accessible as webservices.
57 | *
58 | * @return array
59 | */
60 | public function webEnabledMethods() {
61 | return array(
62 | 'listUpdates' => 'GET',
63 | );
64 | }
65 |
66 | /**
67 | * Set strict access
68 | *
69 | * @param boolean $v
70 | */
71 | public function setStrictAccess($v) {
72 | $this->strictAccess = $v;
73 | }
74 |
75 | /**
76 | * Set the filter date value
77 | * @param string $d
78 | */
79 | public function setFilterDate($d) {
80 | if ($d == 'UpdatedUTC' || $d == 'LastEditedUTC') {
81 | $this->filterDate = $d;
82 | }
83 | }
84 |
85 | /**
86 | * Lists all updated data objects since a particular date that the caller would be interested in
87 | *
88 | * @param date $since
89 | *
90 | * @return DataObjectSet
91 | * The list of data objects that have been created/changed since
92 | *
93 | */
94 | public function listUpdates($since, $system) {
95 | if (!preg_match('/\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/', $since)) {
96 | throw new Exception("Invalid date");
97 | }
98 |
99 | $since = Convert::raw2sql($since);
100 | $system = Convert::raw2sql($system);
101 | $typesToSync = ClassInfo::implementorsOf('Syncroable');
102 |
103 | // restrict to only those items we have been granted sync rights to
104 | $requiredPerm = $this->strictAccess ? 'Syncro' : 'View';
105 |
106 | // we only get objects edited SINCE a certain time
107 | // and that didn't originate in the remote server
108 |
109 | $filter = '"' . Convert::raw2sql($this->filterDate) .'" >= \'' . $since . '\' AND "MasterNode" <> \''. $system .'\'';
110 |
111 | $allUpdates = array();
112 | foreach ($typesToSync as $type) {
113 | $objects = $this->dataService->getAll($type, $filter, '"LastEditedUTC" ASC', "", "", "DataObjectSet", $requiredPerm);
114 | if ($objects && $objects->count()) {
115 | foreach ($objects as $object) {
116 | $toSync = $object->forSyncro();
117 | // some that we force
118 | // note that UpdatedUTC is not sent as it is always a local node specific date
119 | $toSync['MasterNode'] = $object->MasterNode;
120 | $toSync['ContentID'] = $object->ContentID;
121 | $toSync['ClassName'] = $object->ClassName;
122 | $toSync['LastEditedUTC'] = $object->LastEditedUTC;
123 | $toSync['CreatedUTC'] = $object->CreatedUTC;
124 | $toSync['OriginalID'] = $object->OriginalID;
125 |
126 | $allUpdates[] = $toSync;
127 | }
128 | }
129 | }
130 |
131 | $deletes = DataObject::get('SyncroDelete', '"Deleted" >= \'' . $since . '\'');
132 | if ($deletes && $deletes->count()) {
133 | foreach ($deletes as $del) {
134 | $del = array(
135 | 'SYNCRODELETE' => 'DELETE',
136 | 'ContentID' => $del->ContentID,
137 | 'Type' => $del->Type,
138 | 'MasterNode' => SiteConfig::current_site_config()->SystemID,
139 | );
140 | $allUpdates[] = $del;
141 | }
142 | }
143 |
144 | return $allUpdates;
145 | }
146 |
147 | /**
148 | * Get updates from the remote systems
149 | */
150 | public function getUpdates() {
151 | $config = SiteConfig::current_site_config();
152 | $systemId = $config->getSyncroIdentifier();
153 | $nodes = $this->dataService->getAll('RemoteSyncroNode');
154 | foreach ($nodes as $node) {
155 | $url = $node->NodeURL .'/' . self::SERVICE_URL;
156 | $lastSync = $node->LastSync ? $node->LastSync : '2012-01-01 00:00:00';
157 |
158 | $params = array(
159 | 'token' => $node->APIToken,
160 | 'since' => $lastSync,
161 | 'system' => $systemId,
162 | 'rand' => mt_rand(0, 22)
163 | );
164 |
165 | $svc = new RestfulService($url);
166 | $svc->setQueryString($params);
167 | $response = $svc->request();
168 | if ($response->isError()) {
169 | // log and proceed
170 | return;
171 | throw new Exception("Request failed to $url");
172 | }
173 |
174 |
175 | $response = $response->getBody();
176 |
177 | if (is_string($response) && strlen($response)) {
178 |
179 | $data = json_decode($response);
180 | if ($data && is_array($data->response)) {
181 | $this->log->logInfo("Loading " . count($data->response) . " objects from " . $node->NodeURL);
182 | $updateTime = null;
183 | foreach ($data->response as $item) {
184 | $this->processUpdatedObject($item);
185 | if ($item->Title) {
186 | $this->log->logInfo("Sync'd $item->ClassName #$item->ID '" . $item->Title . "'");
187 | } else {
188 | $this->log->logInfo("Sync'd $item->ClassName #$item->ID");
189 | }
190 | if ($item->LastEditedUTC || $item->UpdatedUTC) {
191 | $timeInt = strtotime($item->LastEditedUTC);
192 | if ($timeInt > $updateTime) {
193 | $updateTime = $timeInt;
194 | }
195 | $timeInt = strtotime($item->UpdatedUTC);
196 | if ($timeInt > $updateTime) {
197 | $updateTime = $timeInt;
198 | }
199 | }
200 | }
201 |
202 | if ($updateTime) {
203 | $node->LastSync = gmdate('Y-m-d H:i:s');
204 | $node->write();
205 | }
206 | }
207 | }
208 | }
209 | }
210 |
211 | /**
212 | * process updates based on the provided array of data from the remote system
213 | *
214 | * @param type $json
215 | */
216 | public function processUpdatedObject($object) {
217 | if ($object->ClassName && $object->ContentID && $object->MasterNode) {
218 | // explicitly use the unchecked call here because we don't want to create a new item if it actually turns
219 | // out that it exists and we just don't have write access to it
220 | $existing = DataObject::get_one($object->ClassName, '"ContentID" = \'' . Convert::raw2sql($object->ContentID).'\' AND "MasterNode" = \'' . Convert::raw2sql($object->MasterNode) .'\'');
221 | if ($existing && $existing->exists()) {
222 | if (!$existing->checkPerm('Write')) {
223 | // should we throw an error here?
224 | return;
225 | }
226 | } else {
227 | $cls = $object->ClassName;
228 | $existing = new $cls;
229 | if (isset($object->Title)) {
230 | $existing->Title = $object->Title;
231 | }
232 | // need to initially write, because we're going to totally change the values afterwards
233 | $existing->write();
234 |
235 | $existing->ContentID = $object->ContentID;
236 | $existing->MasterNode = $object->MasterNode;
237 | $existing->OriginalID = $object->OriginalID;
238 | $existing->CreatedUTC = $object->CreatedUTC;
239 | }
240 |
241 | $existing->LastEditedUTC = $object->LastEditedUTC;
242 | $existing->fromSyncro($object);
243 | $existing->write();
244 | } else if ($object->SYNCRODELETE) {
245 | // find and delete relevant item
246 | $existing = DataObject::get_one($object->Type, '"ContentID" = \'' . Convert::raw2sql($object->ContentID).'\' AND "MasterNode" = \'' . Convert::raw2sql($object->MasterNode) .'\'');
247 | if ($existing) {
248 | $existing->delete();
249 | }
250 | }
251 | }
252 |
253 | /**
254 | * Converts an object into a serialised form used for sending over the wire
255 | *
256 | * By default, takes the properties defined directly on it and any has_ones and
257 | * converts to a format readable by the unsyncroObject method
258 | *
259 | * @param DataObject $item
260 | */
261 | public function syncroObject(DataObject $item, $props = null, $hasOne = null) {
262 |
263 | $properties = array();
264 |
265 | if (!$props) {
266 | $props = array_keys($item::$db);
267 | }
268 |
269 | foreach ($props as $name) {
270 | $properties[$name] = $item->$name;
271 | }
272 |
273 | // store owner as email address
274 | if ($item->OwnerID) {
275 | $properties['Restrictable_OwnerEmail'] = $item->Owner()->Email;
276 | }
277 |
278 | $has_ones = array();
279 | if (!$hasOne) {
280 | $hasOne = $item::$has_one;
281 | }
282 | foreach ($hasOne as $name => $type) {
283 | // get the object
284 | $object = $item->getComponent($name);
285 | if ($object && $object->exists()) {
286 | $has_ones[$name] = array('ContentID' => $object->ContentID, 'Type' => $type);
287 | }
288 | }
289 |
290 | $properties['has_one'] = $has_ones;
291 |
292 | return $properties;
293 | }
294 |
295 | /**
296 | * Set properties on an item based on a syncro serialised object
297 | *
298 | * @param stdClass $object
299 | * @param DataObject $into
300 | */
301 | public function unsyncroObject($object, $item) {
302 |
303 | foreach ($object as $prop => $val) {
304 | if ($prop == 'has_one' || $prop == 'many_many') {
305 | continue;
306 | }
307 | $item->$prop = $val;
308 | }
309 |
310 | // set the owner of the object if it exists
311 | if (isset($object->Restrictable_OwnerEmail)) {
312 | $owner = DataObject::get_one('Member', '"Email" = \'' . Convert::raw2sql($object->Restrictable_OwnerEmail) .'\'');
313 | if ($owner && $owner->exists()) {
314 | $item->OwnerID = $owner->ID;
315 | }
316 | }
317 |
318 | if (isset($object->has_one)) {
319 | foreach ($object->has_one as $name => $contentProp) {
320 | $object = DataObject::get_one($contentProp->Type, '"ContentID" = \'' . Convert::raw2sql($contentProp->ContentID) .'\'');
321 | if ($object && $object->exists()) {
322 | $propName = $name.'ID';
323 | $item->$propName = $object->ID;
324 | }
325 | }
326 | }
327 | }
328 | }
329 |
--------------------------------------------------------------------------------
/code/tests/TestSyncrotron.php:
--------------------------------------------------------------------------------
1 | logInWithPermission('ADMIN');
16 |
17 | $json = $this->updateJSON();
18 |
19 | $data = json_decode($json);
20 |
21 | foreach ($data->response as $item) {
22 | singleton('SyncrotronService')->processUpdatedObject($item);
23 | }
24 | }
25 |
26 | protected function updateJSON() {
27 | $json = <<Title = 'Whatever';
36 | $obj->write();
37 | $this->assertTrue($obj->ID > 0);
38 |
39 | $obj->delete();
40 |
41 | $this->assertTrue($obj->ID == 0);
42 |
43 | $delete = DataObject::get_one('SyncroDelete', '"ContentID" = \'' . $obj->ContentID .'\'');
44 | $this->assertNotNull($delete);
45 | $this->assertTrue($delete->ID > 0);
46 | }
47 | }
48 |
49 | class SyncroTestObject extends DataObject implements TestOnly, Syncroable {
50 | public static $db = array(
51 | 'Title' => 'Varchar',
52 | );
53 |
54 | public static $extensions = array(
55 | 'SyncroableExtension'
56 | );
57 |
58 | public function forSyncro() {
59 | $props = singleton('SyncrotronService')->syncroObject($this);
60 | return $props;
61 | }
62 |
63 | public function fromSyncro($properties) {
64 | singleton('SyncrotronService')->unsyncroObject($properties, $this);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/code/thirdparty/_manifest_exclude:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nyeholt/silverstripe-syncrotron/bb93691757b283a858a3e460d547c697e2054651/code/thirdparty/_manifest_exclude
--------------------------------------------------------------------------------
/code/thirdparty/klogger/.gitignore:
--------------------------------------------------------------------------------
1 | src/test.php
2 | test/
3 | test/*
4 | test.php
5 |
--------------------------------------------------------------------------------
/code/thirdparty/klogger/README.markdown:
--------------------------------------------------------------------------------
1 | # KLogger: A Simple Logging Class For PHP
2 |
3 | A project written by Kenny Katzgrau and originally hosted at
4 | [CodeFury.net](http://codefury.net/projects/klogger/). This marks the
5 | development of a newer version of KLogger.
6 |
7 | ## About
8 |
9 | KLogger is an easy-to-use logging class for PHP. It supports standard log levels
10 | like debug, info, warn, error, and fatal. Additionally, it isn't naive about
11 | file permissions (which is expected). It was meant to be a class that you could
12 | quickly include into a project and have working right away.
13 |
14 | The class was written in 2008, but I have since received a number of emails both
15 | saying 'thanks' and asking me to add features.
16 |
17 | This github project will host the development of the next version of KLogger.
18 | The original version of KLogger is tagged as version 0.1, and is available for
19 | download [here](http://github.com/katzgrau/KLogger/downloads).
20 |
21 | ## Basic Usage
22 |
23 | $log = new KLogger('/var/log/'); # Specify the log directory
24 | $log->logInfo('Returned a million search results'); //Prints to the log file
25 | $log->logFatal('Oh dear.'); //Prints to the log file
26 |
27 | ## Goals
28 |
29 | All of KLogger's internal goals have been met (there used to be a list here).
30 | If you have a feature request, send it to katzgrau@gmail.com
31 |
32 | ## Why use KLogger?
33 |
34 | Why not? Just drop it in and go. If it saves you time and does what you need,
35 | go for it! Take a line from the book of our C-code fathers: "`build` upon the
36 | work of others".
37 |
38 | ## Who uses KLogger?
39 |
40 | Klogger has been used in projects at:
41 |
42 | * The University of Iowa
43 | * The University of Laverne
44 | * The New Jersey Institute of Technology
45 | * Middlesex Hospital in NJ
46 |
47 | Additionally, it's been used in numerous projects, both commercial and personal.
48 |
49 | ## Special Thanks
50 |
51 | Special thanks to all contributors, which right now includes two people:
52 |
53 | [Tim Kinnane](http://twitter.com/etherealtim)
54 | [Brian Fenton](http://github.com/fentie)
55 |
56 | ## License
57 |
58 | The MIT License
59 |
60 | Copyright (c) 2008-2010 Kenny Katzgrau
61 |
62 | Permission is hereby granted, free of charge, to any person obtaining a copy
63 | of this software and associated documentation files (the "Software"), to deal
64 | in the Software without restriction, including without limitation the rights
65 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
66 | copies of the Software, and to permit persons to whom the Software is
67 | furnished to do so, subject to the following conditions:
68 |
69 | The above copyright notice and this permission notice shall be included in
70 | all copies or substantial portions of the Software.
71 |
72 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
73 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
74 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
75 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
76 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
77 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
78 | THE SOFTWARE.
79 |
--------------------------------------------------------------------------------
/code/thirdparty/klogger/example/example.php:
--------------------------------------------------------------------------------
1 | logInfo('Info Test');
9 | $log->logNotice('Notice Test');
10 | $log->logWarn('Warn Test');
11 | $log->logError('Error Test');
12 | $log->logFatal('Fatal Test');
13 | $log->logAlert('Alert Test');
14 | $log->logCrit('Crit test');
15 | $log->logEmerg('Emerg Test');
16 |
--------------------------------------------------------------------------------
/code/thirdparty/klogger/src/KLogger.php:
--------------------------------------------------------------------------------
1 | logInfo('Returned a million search results'); //Prints to the log file
11 | * $log->logFatal('Oh dear.'); //Prints to the log file
12 | * $log->logDebug('x = 5'); //Prints nothing due to current severity threshhold
13 | *
14 | * @author Kenny Katzgrau
15 | * @since July 26, 2008
16 | * @link http://codefury.net
17 | * @version 0.1
18 | */
19 |
20 | /**
21 | * Class documentation
22 | */
23 | class KLogger
24 | {
25 | /**
26 | * Error severity, from low to high. From BSD syslog RFC, secion 4.1.1
27 | * @link http://www.faqs.org/rfcs/rfc3164.html
28 | */
29 | const EMERG = 0; // Emergency: system is unusable
30 | const ALERT = 1; // Alert: action must be taken immediately
31 | const CRIT = 2; // Critical: critical conditions
32 | const ERR = 3; // Error: error conditions
33 | const WARN = 4; // Warning: warning conditions
34 | const NOTICE = 5; // Notice: normal but significant condition
35 | const INFO = 6; // Informational: informational messages
36 | const DEBUG = 7; // Debug: debug messages
37 |
38 | //custom logging level
39 | /**
40 | * Log nothing at all
41 | */
42 | const OFF = 8;
43 | /**
44 | * Alias for CRIT
45 | * @deprecated
46 | */
47 | const FATAL = 2;
48 |
49 | /**
50 | * Internal status codes
51 | */
52 | const STATUS_LOG_OPEN = 1;
53 | const STATUS_OPEN_FAILED = 2;
54 | const STATUS_LOG_CLOSED = 3;
55 |
56 | /**
57 | * Current status of the log file
58 | * @var integer
59 | */
60 | private $_logStatus = self::STATUS_LOG_CLOSED;
61 | /**
62 | * Holds messages generated by the class
63 | * @var array
64 | */
65 | private $_messageQueue = array();
66 | /**
67 | * Path to the log file
68 | * @var string
69 | */
70 | private $_logFilePath = null;
71 | /**
72 | * Current minimum logging threshold
73 | * @var integer
74 | */
75 | private $_severityThreshold = self::INFO;
76 | /**
77 | * This holds the file handle for this instance's log file
78 | * @var resource
79 | */
80 | private $_fileHandle = null;
81 |
82 | /**
83 | * Standard messages produced by the class. Can be modified for il8n
84 | * @var array
85 | */
86 | private $_messages = array(
87 | //'writefail' => 'The file exists, but could not be opened for writing. Check that appropriate permissions have been set.',
88 | 'writefail' => 'The file could not be written to. Check that appropriate permissions have been set.',
89 | 'opensuccess' => 'The log file was opened successfully.',
90 | 'openfail' => 'The file could not be opened. Check permissions.',
91 | );
92 |
93 | /**
94 | * Default severity of log messages, if not specified
95 | * @var integer
96 | */
97 | private static $_defaultSeverity = self::DEBUG;
98 | /**
99 | * Valid PHP date() format string for log timestamps
100 | * @var string
101 | */
102 | private static $_dateFormat = 'Y-m-d G:i:s';
103 | /**
104 | * Octal notation for default permissions of the log file
105 | * @var integer
106 | */
107 | private static $_defaultPermissions = 0777;
108 | /**
109 | * Array of KLogger instances, part of Singleton pattern
110 | * @var array
111 | */
112 | private static $instances = array();
113 |
114 | /**
115 | * Partially implements the Singleton pattern. Each $logDirectory gets one
116 | * instance.
117 | *
118 | * @param string $logDirectory File path to the logging directory
119 | * @param integer $severity One of the pre-defined severity constants
120 | * @return KLogger
121 | */
122 | public static function instance($logDirectory = false, $severity = false)
123 | {
124 | if ($severity === false) {
125 | $severity = self::$_defaultSeverity;
126 | }
127 |
128 | if ($logDirectory === false) {
129 | if (count(self::$instances) > 0) {
130 | return current(self::$instances);
131 | } else {
132 | $logDirectory = dirname(__FILE__);
133 | }
134 | }
135 |
136 | if (in_array($logDirectory, self::$instances)) {
137 | return self::$instances[$logDirectory];
138 | }
139 |
140 | self::$instances[$logDirectory] = new self($logDirectory, $severity);
141 |
142 | return self::$instances[$logDirectory];
143 | }
144 |
145 | /**
146 | * Class constructor
147 | *
148 | * @param string $logDirectory File path to the logging directory
149 | * @param integer $severity One of the pre-defined severity constants
150 | * @return void
151 | */
152 | public function __construct($logDirectory, $severity)
153 | {
154 | $logDirectory = rtrim($logDirectory, '\\/');
155 |
156 | if ($severity === self::OFF) {
157 | return;
158 | }
159 |
160 | $this->_logFilePath = $logDirectory
161 | . DIRECTORY_SEPARATOR
162 | . 'log_'
163 | . date('Y-m-d')
164 | . '.txt';
165 |
166 | $this->_severityThreshold = $severity;
167 | if (!file_exists($logDirectory)) {
168 | mkdir($logDirectory, self::$_defaultPermissions, true);
169 | }
170 |
171 | if (file_exists($this->_logFilePath) && !is_writable($this->_logFilePath)) {
172 | $this->_logStatus = self::STATUS_OPEN_FAILED;
173 | $this->_messageQueue[] = $this->_messages['writefail'];
174 | return;
175 | }
176 |
177 | if (($this->_fileHandle = fopen($this->_logFilePath, 'a'))) {
178 | $this->_logStatus = self::STATUS_LOG_OPEN;
179 | $this->_messageQueue[] = $this->_messages['opensuccess'];
180 | } else {
181 | $this->_logStatus = self::STATUS_OPEN_FAILED;
182 | $this->_messageQueue[] = $this->_messages['openfail'];
183 | }
184 | }
185 |
186 | /**
187 | * Class destructor
188 | */
189 | public function __destruct()
190 | {
191 | if ($this->_fileHandle) {
192 | fclose($this->_fileHandle);
193 | }
194 | }
195 | /**
196 | * Writes a $line to the log with a severity level of DEBUG
197 | *
198 | * @param string $line Information to log
199 | * @return void
200 | */
201 | public function logDebug($line)
202 | {
203 | $this->log($line, self::DEBUG);
204 | }
205 |
206 | /**
207 | * Returns (and removes) the last message from the queue.
208 | * @return string
209 | */
210 | public function getMessage()
211 | {
212 | return array_pop($this->_messageQueue);
213 | }
214 |
215 | /**
216 | * Returns the entire message queue (leaving it intact)
217 | * @return array
218 | */
219 | public function getMessages()
220 | {
221 | return $this->_messageQueue;
222 | }
223 |
224 | /**
225 | * Empties the message queue
226 | * @return void
227 | */
228 | public function clearMessages()
229 | {
230 | $this->_messageQueue = array();
231 | }
232 |
233 | /**
234 | * Sets the date format used by all instances of KLogger
235 | *
236 | * @param string $dateFormat Valid format string for date()
237 | */
238 | public static function setDateFormat($dateFormat)
239 | {
240 | self::$_dateFormat = $dateFormat;
241 | }
242 |
243 | /**
244 | * Writes a $line to the log with a severity level of INFO. Any information
245 | * can be used here, or it could be used with E_STRICT errors
246 | *
247 | * @param string $line Information to log
248 | * @return void
249 | */
250 | public function logInfo($line)
251 | {
252 | $this->log($line, self::INFO);
253 | }
254 |
255 | /**
256 | * Writes a $line to the log with a severity level of NOTICE. Generally
257 | * corresponds to E_STRICT, E_NOTICE, or E_USER_NOTICE errors
258 | *
259 | * @param string $line Information to log
260 | * @return void
261 | */
262 | public function logNotice($line)
263 | {
264 | $this->log($line, self::NOTICE);
265 | }
266 |
267 | /**
268 | * Writes a $line to the log with a severity level of WARN. Generally
269 | * corresponds to E_WARNING, E_USER_WARNING, E_CORE_WARNING, or
270 | * E_COMPILE_WARNING
271 | *
272 | * @param string $line Information to log
273 | * @return void
274 | */
275 | public function logWarn($line)
276 | {
277 | $this->log($line, self::WARN);
278 | }
279 |
280 | /**
281 | * Writes a $line to the log with a severity level of ERR. Most likely used
282 | * with E_RECOVERABLE_ERROR
283 | *
284 | * @param string $line Information to log
285 | * @return void
286 | */
287 | public function logError($line)
288 | {
289 | $this->log($line, self::ERR);
290 | }
291 |
292 | /**
293 | * Writes a $line to the log with a severity level of FATAL. Generally
294 | * corresponds to E_ERROR, E_USER_ERROR, E_CORE_ERROR, or E_COMPILE_ERROR
295 | *
296 | * @param string $line Information to log
297 | * @return void
298 | * @deprecated Use logCrit
299 | */
300 | public function logFatal($line)
301 | {
302 | $this->log($line, self::FATAL);
303 | }
304 |
305 | /**
306 | * Writes a $line to the log with a severity level of ALERT.
307 | *
308 | * @param string $line Information to log
309 | * @return void
310 | */
311 | public function logAlert($line)
312 | {
313 | $this->log($line, self::ALERT);
314 | }
315 |
316 | /**
317 | * Writes a $line to the log with a severity level of CRIT.
318 | *
319 | * @param string $line Information to log
320 | * @return void
321 | */
322 | public function logCrit($line)
323 | {
324 | $this->log($line, self::CRIT);
325 | }
326 |
327 | /**
328 | * Writes a $line to the log with a severity level of EMERG.
329 | *
330 | * @param string $line Information to log
331 | * @return void
332 | */
333 | public function logEmerg($line)
334 | {
335 | $this->log($line, self::EMERG);
336 | }
337 |
338 | /**
339 | * Writes a $line to the log with the given severity
340 | *
341 | * @param string $line Text to add to the log
342 | * @param integer $severity Severity level of log message (use constants)
343 | */
344 | public function log($line, $severity)
345 | {
346 | if ($this->_severityThreshold >= $severity) {
347 | $status = $this->_getTimeLine($severity);
348 | $this->writeFreeFormLine("$status $line \n");
349 | }
350 | }
351 |
352 | /**
353 | * Writes a line to the log without prepending a status or timestamp
354 | *
355 | * @param string $line Line to write to the log
356 | * @return void
357 | */
358 | public function writeFreeFormLine($line)
359 | {
360 | if ($this->_logStatus == self::STATUS_LOG_OPEN
361 | && $this->_severityThreshold != self::OFF) {
362 | if (fwrite($this->_fileHandle, $line) === false) {
363 | $this->_messageQueue[] = $this->_messages['writefail'];
364 | }
365 | }
366 | }
367 |
368 | private function _getTimeLine($level)
369 | {
370 | $time = date(self::$_dateFormat);
371 |
372 | switch ($level) {
373 | case self::EMERG:
374 | return "$time - EMERG -->";
375 | case self::ALERT:
376 | return "$time - ALERT -->";
377 | case self::CRIT:
378 | return "$time - CRIT -->";
379 | case self::FATAL: # FATAL is an alias of CRIT
380 | return "$time - FATAL -->";
381 | case self::NOTICE:
382 | return "$time - NOTICE -->";
383 | case self::INFO:
384 | return "$time - INFO -->";
385 | case self::WARN:
386 | return "$time - WARN -->";
387 | case self::DEBUG:
388 | return "$time - DEBUG -->";
389 | case self::ERR:
390 | return "$time - ERROR -->";
391 | default:
392 | return "$time - LOG -->";
393 | }
394 | }
395 | }
396 |
--------------------------------------------------------------------------------