Open Monograph Press  1.1
 All Classes Namespaces Functions Variables Groups Pages
PKPUsageEventPlugin.inc.php
1 <?php
2 
18 import('lib.pkp.classes.plugins.GenericPlugin');
19 
20 // User classification types.
21 define('USAGE_EVENT_PLUGIN_CLASSIFICATION_BOT', 'bot');
22 define('USAGE_EVENT_PLUGIN_CLASSIFICATION_ADMIN', 'administrative');
23 
24 abstract class PKPUsageEventPlugin extends GenericPlugin {
25 
26  //
27  // Implement methods from PKPPlugin.
28  //
32  function register($category, $path) {
33  $success = parent::register($category, $path);
34 
35  if ($success) {
36  $eventHooks = $this->getEventHooks();
37  foreach ($eventHooks as $hook) {
38  HookRegistry::register($hook, array($this, 'getUsageEvent'));
39  }
40  }
41 
42  return $success;
43  }
44 
48  function getName() {
49  return 'usageeventplugin';
50  }
51 
56  return PKP_LIB_PATH . DIRECTORY_SEPARATOR . $this->getPluginPath() . DIRECTORY_SEPARATOR . 'settings.xml';
57  }
58 
62  function getDisplayName() {
63  return __('plugins.generic.usageEvent.displayName');
64  }
65 
69  function getDescription() {
70  return __('plugins.generic.usageEvent.description');
71  }
72 
76  function getEnabled() {
77  return true;
78  }
79 
83  function isSitePlugin() {
84  return true;
85  }
86 
90  function getManagementVerbs() {
91  return array();
92  }
93 
94 
95  //
96  // Public methods.
97  //
102  function getUniqueSiteId() {
103  return $this->getSetting(0, 'uniqueSiteId');
104  }
105 
106 
107  //
108  // Hook implementations.
109  //
113  function getUsageEvent($hookName, $args) {
114  // Check if we have a registration to receive the usage event.
115  $hooks = HookRegistry::getHooks();
116  if (array_key_exists('UsageEventPlugin::getUsageEvent', $hooks)) {
117 
118  $usageEvent = $this->buildUsageEvent($hookName, $args);
119  HookRegistry::call('UsageEventPlugin::getUsageEvent', array_merge(array($hookName, $usageEvent), $args));
120  }
121  return false;
122  }
123 
124 
125  //
126  // Protected methods.
127  //
133  protected function getEventHooks() {
134  return array(
135  'TemplateManager::display',
136  'FileManager::downloadFileFinished'
137  );
138  }
139 
146  protected function buildUsageEvent($hookName, $args) {
147  // Finished downloading a file?
148  if ($hookName == 'FileManager::downloadFileFinished') {
149  // The usage event for this request is already build and
150  // passed to any other registered hook.
151  return null;
152  }
153 
154  $application = Application::getApplication();
155  $request = $application->getRequest();
156  $router = $request->getRouter(); /* @var $router PageRouter */
157  $templateMgr = $args[0]; /* @var $templateMgr TemplateManager */
158 
159  // We are just interested in page requests.
160  if (!is_a($router, 'PageRouter')) return false;
161 
162  // Check whether we are in journal context.
163  $context = $router->getContext($request);
164  if (!$context) return false;
165 
166  // Prepare request information.
167  list($pubObject, $downloadSuccess, $assocType, $idParams, $canonicalUrlPage, $canonicalUrlOp, $canonicalUrlParams) =
168  $this->getUsageEventData($hookName, $args, $request, $router, $templateMgr, $context);
169 
170  if (!$pubObject) return false;
171 
172  // Timestamp.
173  $time = Core::getCurrentDate();
174 
175  // Actual document size, MIME type.
176  $htmlPageAssocTypes = $this->getHtmlPageAssocTypes();
177  if (in_array($assocType, $htmlPageAssocTypes)) {
178  // HTML pages with no file downloads.
179  $docSize = 0;
180  $mimeType = 'text/html';
181  } else {
182  // Files.
183  $docSize = (int)$pubObject->getFileSize();
184  $mimeType = $pubObject->getFileType();
185  }
186 
187  $canonicalUrl = $router->url(
188  $request, null, $canonicalUrlPage, $canonicalUrlOp, $canonicalUrlParams
189  );
190 
191  // Public identifiers.
192  // 1) A unique system internal ID that will help us to easily attribute
193  // statistics to a specific publication object.
194  array_unshift($idParams, 'c' . $context->getId());
195  $siteId = $this->getUniqueSiteId();
196  if (empty($siteId)) {
197  // Create a globally unique, persistent site ID
198  // so that we can uniquely identify publication
199  // objects from this site, even if the URL or any
200  // other externally influenced information changes.
201  $siteId = uniqid();
202  $this->updateSetting(0, 'uniqueSiteId', $siteId);
203  }
204  array_unshift($idParams, $siteId);
205  $applicationName = $application->getName();
206  $applicationId = $applicationName . ':' . implode('-', $idParams);
207  $idKey = 'other::' . $applicationName;
208  $identifiers = array($idKey => $applicationId);
209 
210  // 2) Standardized public identifiers, e.g. DOI, URN, etc.
211  if ($this->isPubIdObjectType($pubObject)) {
212  $pubIdPlugins = PluginRegistry::loadCategory('pubIds', true, $context->getId());
213  if (is_array($pubIdPlugins)) {
214  foreach ($pubIdPlugins as $pubIdPlugin) {
215  if (!$pubIdPlugin->getEnabled()) continue;
216  $pubId = $pubIdPlugin->getPubId($pubObject);
217  if ($pubId) {
218  $identifiers[$pubIdPlugin->getPubIdType()] = $pubId;
219  }
220  }
221  }
222  }
223 
224  // Service URI.
225  $serviceUri = $router->url($request, $context->getPath());
226 
227  // IP and Host.
228  $ip = $request->getRemoteAddr();
229  $host = null;
230  if (isset($_SERVER['REMOTE_HOST'])) {
231  // We do NOT actively look up the remote host to
232  // avoid the performance penalty. We only set the remote
233  // host if we get it "for free".
234  $host = $_SERVER['REMOTE_HOST'];
235  }
236 
237  // HTTP user agent.
238  $userAgent = $request->getUserAgent();
239 
240  // HTTP referrer.
241  $referrer = (isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : null);
242 
243  // User and roles.
244  $user = $request->getUser();
245  $roles = array();
246  if ($user) {
247  $roleDao = DAORegistry::getDAO('RoleDAO'); /* @var $roleDao PKPRoleDAO */
248  $rolesByContext = $roleDao->getByUserIdGroupedByContext($user->getId());
249  foreach (array(CONTEXT_SITE, $context->getId()) as $workingContext) {
250  if(isset($rolesByContext[$workingContext])) {
251  foreach ($rolesByContext[$workingContext] as $roleId => $role) {
252  $roles[] = $roleId;
253  }
254  }
255  }
256  }
257 
258  // Try a simple classification of the request.
259  $classification = null;
260  if (!empty($roles)) {
261  // Access by editors, authors, etc.
262  $internalRoles = array_diff($roles, array(ROLE_ID_READER));
263  if (!empty($internalRoles)) {
264  $classification = USAGE_EVENT_PLUGIN_CLASSIFICATION_ADMIN;
265  }
266  }
267  if ($request->isBot()) {
268  // The bot classification overwrites other classifications.
269  $classification = USAGE_EVENT_PLUGIN_CLASSIFICATION_BOT;
270  }
271  // TODO: Classify LOCKSS or similar as 'internal' access.
272 
273  /*
274  * Comparison of our event log format with Apache log parameters...
275  *
276  * 1) default parameters:
277  * %h: remote hostname or IP => $ip, $host
278  * %l: remote logname (identd) => not supported, see $user, $roles instead
279  * %u: remote user => not supported, see $user, $roles instead
280  * %t: request time => $time
281  * %r: query => derived objects: $pubObject, $assocType, $canonicalUrl, $identifiers, $serviceUri, $classification
282  * %s: status => not supported (always 200 in our case)
283  * %b: response size => $docSize
284  *
285  * 2) other common parameters
286  * %O: bytes sent => not supported (cannot be reliably determined from within PHP)
287  * %X: connection status => $downloadSuccess (not reliable!)
288  * %{ContentType}o: => $mimeType
289  * %{User-agent}i: => $userAgent
290  * %{Referer}i: => $referrer
291  *
292  * Several items, e.g. time etc., may differ from what Apache
293  * would actually log. But the differences do not matter for our use
294  * cases.
295  */
296 
297  // Collect all information into an array.
298  $usageEvent = compact(
299  'time', 'pubObject', 'assocType', 'canonicalUrl', 'mimeType',
300  'identifiers', 'docSize', 'downloadSuccess', 'serviceUri',
301  'ip', 'host', 'user', 'roles', 'userAgent', 'referrer',
302  'classification'
303  );
304 
305  return $usageEvent;
306  }
307 
324  protected function getUsageEventData($hookName, $hookArgs, $request, $router, $templateMgr, $context) {
325  $nullVar = null;
326  $pubObject = $nullVar;
327  $downloadSuccess = false;
328  $canonicalUrlPage = $canonicalUrlOp = $assocType = null;
329  $canonicalUrlParams = $idParams = array();
330 
331  if ($hookName == 'TemplateManager::display') {
332  $page = $router->getRequestedPage($request);
333  $op = $router->getRequestedOp($request);
334 
335  // First check for a context index page view.
336  if (($page == 'index' || empty($page)) && $op == 'index') {
337  $pubObject = $templateMgr->get_template_vars('currentContext');
338  if (is_a($pubObject, 'Context')) {
339  $assocType = Application::getContextAssocType();
340  $canonicalUrlOp = '';
341  $canonicalUrlPage = 'index';
342  $downloadSuccess = true;
343  }
344  }
345  }
346 
347  return array($pubObject, $downloadSuccess, $assocType, $idParams, $canonicalUrlPage, $canonicalUrlOp, $canonicalUrlParams);
348  }
349 
350  //
351  // Abstract protected methods.
352  //
358  abstract protected function getHtmlPageAssocTypes();
359 
366  abstract protected function isPubIdObjectType($pubObject);
367 
368 }
369 
370 ?>
static & getDAO($name, $dbconn=null)
static & loadCategory($category, $enabledOnly=false, $mainContextId=null)
getUsageEvent($hookName, $args)
buildUsageEvent($hookName, $args)
Abstract class for generic plugins.
static call($hookName, $args=null)
getPluginPath()
Definition: Plugin.inc.php:324
isPubIdObjectType($pubObject)
static getContextAssocType()
getSetting($contextId, $name)
Definition: Plugin.inc.php:366
getUsageEventData($hookName, $hookArgs, $request, $router, $templateMgr, $context)
static & getHooks()
static register($hookName, $callback)
updateSetting($contextId, $name, $value, $type=null)
Definition: Plugin.inc.php:378
Base class for usage event plugin. Provide usage events to other statistics plugins.
static getCurrentDate($ts=null)
Definition: Core.inc.php:95