Open Journal Systems  3.3.0
PKPAcronPlugin.inc.php
1 <?php
2 
18 import('lib.pkp.classes.plugins.GenericPlugin');
19 import('lib.pkp.classes.scheduledTask.ScheduledTaskHelper');
20 
21 // TODO: Error handling. If a scheduled task encounters an error...?
22 
24 
26  var $_workingDir;
27 
30 
34  function register($category, $path, $mainContextId = null) {
35  $success = parent::register($category, $path, $mainContextId);
36  HookRegistry::register('Installer::postInstall', array(&$this, 'callbackPostInstall'));
37 
38  if (!Config::getVar('general', 'installed') || defined('RUNNING_UPGRADE')) return $success;
39  if ($success) {
40  $this->addLocaleData();
41  HookRegistry::register('LoadHandler',array(&$this, 'callbackLoadHandler'));
42  // Need to reload cron tab on possible enable or disable generic plugin actions.
43  HookRegistry::register('PluginGridHandler::plugin', array(&$this, 'callbackManage'));
44  }
45  return $success;
46  }
47 
51  function isSitePlugin() {
52  // This is a site-wide plugin.
53  return true;
54  }
55 
59  function getName() {
60  return 'acronPlugin';
61  }
62 
66  function getDisplayName() {
67  return __('plugins.generic.acron.name');
68  }
69 
73  function getDescription() {
74  return __('plugins.generic.acron.description');
75  }
76 
81  return PKP_LIB_PATH . DIRECTORY_SEPARATOR . $this->getPluginPath() . '/settings.xml';
82  }
83 
87  function getActions($request, $actionArgs) {
88  import('lib.pkp.classes.linkAction.request.AjaxAction');
89  $router = $request->getRouter();
90  return array_merge(
91  $this->getEnabled()?array(
92  new LinkAction(
93  'reload',
94  new AjaxAction(
95  $router->url($request, null, null, 'manage', null, array('verb' => 'reload', 'plugin' => $this->getName(), 'category' => 'generic'))
96  ),
97  __('plugins.generic.acron.reload'),
98  null
99  )
100  ):array(),
101  parent::getActions($request, $actionArgs)
102  );
103  }
104 
108  function manage($args, $request) {
109  switch($request->getUserVar('verb')) {
110  case 'reload':
111  $this->_parseCrontab();
112  $notificationManager = new NotificationManager();
113  $user = $request->getUser();
114  $notificationManager->createTrivialNotification(
115  $user->getId(), NOTIFICATION_TYPE_SUCCESS,
116  array('contents' => __('plugins.generic.acron.tasksReloaded'))
117  );
118  return DAO::getDataChangedEvent();
119  }
120  return parent::manage($args, $request);
121  }
122 
130  function callbackPostInstall($hookName, $args) {
131  $this->_parseCrontab();
132  return false;
133  }
134 
142  function callbackLoadHandler($hookName, $args) {
143  $request = Application::get()->getRequest();
144  $router = $request->getRouter();
145  // Avoid controllers requests because of the shutdown function usage.
146  if (!is_a($router, 'PKPPageRouter')) return false;
147 
148  $tasksToRun = $this->_getTasksToRun();
149  if (!empty($tasksToRun)) {
150  // Save the current working directory, so we can fix
151  // it inside the shutdown function.
152  $this->_workingDir = getcwd();
153 
154  // Save the tasks to be executed.
155  $this->_tasksToRun = $tasksToRun;
156 
157  // Need output buffering to send a finish message
158  // to browser inside the shutdown function. Couldn't
159  // do without the buffer.
160  ob_start();
161 
162  // This callback will be used as soon as the main script
163  // is finished. It will not stop running, even if the user cancels
164  // the request or the time limit is reach.
165  register_shutdown_function(array(&$this, 'shutdownFunction'));
166  }
167 
168  return false;
169  }
170 
178  function callbackManage($hookName, $args) {
179  $verb = $args[0];
180  $plugin = $args[4]; /* @var $plugin LazyLoadPlugin */
181 
182  // Only interested in plugins that can be enabled/disabled.
183  if (!is_a($plugin, 'LazyLoadPlugin')) return false;
184 
185  // Only interested in enable/disable actions.
186  if ($verb !== 'enable' && $verb !== 'disable') return false;
187 
188  // Check if the plugin wants to add its own
189  // scheduled task into the cron tab.
190 
191  foreach (HookRegistry::getHooks('AcronPlugin::parseCronTab') as $hookPriorityList) {
192  foreach ($hookPriorityList as $priority => $callback) {
193  if ($callback[0] == $plugin) {
194  $this->_parseCrontab();
195  break;
196  }
197  }
198  }
199 
200  return false;
201  }
202 
206  function shutdownFunction() {
207  // After PHP 4.1.0, the execution of this callback is part of the request,
208  // so users will have no response until it finishes executing it. We avoid
209  // that by sending headers to the browser that makes them believe the script
210  // is over.
211  header("Connection: close");
212  // This header is needed so avoid using any kind of compression. If zlib is
213  // enabled, for example, the buffer will not output until the end of the
214  // script execution.
215  header("Content-Encoding: none");
216  header("Content-Length: " . ob_get_length());
217  ob_end_flush();
218  flush();
219 
220  set_time_limit(0);
221 
222  // Fix the current working directory. See
223  // http://www.php.net/manual/en/function.register-shutdown-function.php#92657
224  chdir($this->_workingDir);
225 
226  $taskDao = DAORegistry::getDAO('ScheduledTaskDAO');
227  foreach($this->_tasksToRun as $task) {
228  // Strip off the package name(s) to get the base class name
229  $className = $task['className'];
230  $pos = strrpos($className, '.');
231  if ($pos === false) {
232  $baseClassName = $className;
233  } else {
234  $baseClassName = substr($className, $pos+1);
235  }
236  $taskArgs = array();
237  if (isset($task['args'])) {
238  $taskArgs = $task['args'];
239  }
240 
241  // There's a race here. Several requests may come in closely spaced.
242  // Each may decide it's time to run scheduled tasks, and more than one
243  // can happily go ahead and do it before the "last run" time is updated.
244  // By updating the last run time as soon as feasible, we can minimize
245  // the race window. See bug #8737.
246  $tasksToRun = $this->_getTasksToRun();
247  $updateResult = 0;
248  if (in_array($task, $tasksToRun, true)) {
249  $updateResult = $taskDao->updateLastRunTime($className, time());
250  }
251 
252  if ($updateResult === false || $updateResult === 1) {
253  // DB doesn't support the get affected rows used inside update method, or one row was updated when we introduced a new last run time.
254  // Load and execute the task.
255  import($className);
256  $task = new $baseClassName($taskArgs);
257  $task->execute();
258  }
259  }
260  }
261 
262 
263  //
264  // Private helper methods.
265  //
270  function _parseCrontab() {
271  $xmlParser = new XMLParser();
272 
273  $taskFilesPath = array();
274 
275  // Load all plugins so any plugin can register a crontab.
277 
278  // Let plugins register their scheduled tasks too.
279  HookRegistry::call('AcronPlugin::parseCronTab', array(&$taskFilesPath)); // Reference needed.
280 
281  // Add the default tasks file.
282  $taskFilesPath[] = 'registry/scheduledTasks.xml'; // TODO: make this a plugin setting, rather than assuming.
283 
284  $tasks = array();
285  foreach ($taskFilesPath as $filePath) {
286  $tree = $xmlParser->parse($filePath);
287 
288  if (!$tree) {
289  fatalError('Error parsing scheduled tasks XML file: ' . $filePath);
290  }
291 
292  foreach ($tree->getChildren() as $task) {
293  $frequency = $task->getChildByName('frequency');
294 
295  $args = ScheduledTaskHelper::getTaskArgs($task);
296 
297  // Tasks without a frequency defined, or defined to zero, will run on every request.
298  // To avoid that happening (may cause performance problems) we
299  // setup a default period of time.
300  $setDefaultFrequency = true;
301  $minHoursRunPeriod = 24;
302  if ($frequency) {
303  $frequencyAttributes = $frequency->getAttributes();
304  if (is_array($frequencyAttributes)) {
305  foreach($frequencyAttributes as $key => $value) {
306  if ($value != 0) {
307  $setDefaultFrequency = false;
308  break;
309  }
310  }
311  }
312  }
313  $tasks[] = array(
314  'className' => $task->getAttribute('class'),
315  'frequency' => $setDefaultFrequency ? array('hour' => $minHoursRunPeriod) : $frequencyAttributes,
316  'args' => $args
317  );
318  }
319  }
320 
321  // Store the object.
322  $this->updateSetting(0, 'crontab', $tasks, 'object');
323  }
324 
329  function _getTasksToRun() {
330  $tasksToRun = array();
331  $isEnabled = $this->getSetting(0, 'enabled');
332 
333  if($isEnabled) {
334  $taskDao = DAORegistry::getDAO('ScheduledTaskDAO');
335 
336  // Grab the scheduled scheduled tree
337  $scheduledTasks = $this->getSetting(0, 'crontab');
338  if(is_null($scheduledTasks)) {
339  $this->_parseCrontab();
340  $scheduledTasks = $this->getSetting(0, 'crontab');
341  }
342 
343  foreach($scheduledTasks as $task) {
344  // We don't allow tasks without frequency, see _parseCronTab().
345  $frequency = new XMLNode();
346  $frequency->setAttribute(key($task['frequency']), current($task['frequency']));
347  $canExecute = ScheduledTaskHelper::checkFrequency($task['className'], $frequency);
348 
349  if ($canExecute) {
350  $tasksToRun[] = $task;
351  }
352  }
353  }
354 
355  return $tasksToRun;
356  }
357 }
358 
PluginRegistry\loadAllPlugins
static loadAllPlugins($enabledOnly=false)
Definition: PluginRegistry.inc.php:208
PKPAcronPlugin\$_workingDir
$_workingDir
Definition: PKPAcronPlugin.inc.php:29
Plugin\updateSetting
updateSetting($contextId, $name, $value, $type=null)
Definition: Plugin.inc.php:495
DAORegistry\getDAO
static & getDAO($name, $dbconn=null)
Definition: DAORegistry.inc.php:57
PKPAcronPlugin\shutdownFunction
shutdownFunction()
Definition: PKPAcronPlugin.inc.php:212
PKPAcronPlugin\callbackLoadHandler
callbackLoadHandler($hookName, $args)
Definition: PKPAcronPlugin.inc.php:148
PKPAcronPlugin\getDisplayName
getDisplayName()
Definition: PKPAcronPlugin.inc.php:72
PKPAcronPlugin\callbackPostInstall
callbackPostInstall($hookName, $args)
Definition: PKPAcronPlugin.inc.php:136
PKPAcronPlugin\_parseCrontab
_parseCrontab()
Definition: PKPAcronPlugin.inc.php:276
AjaxAction
Class defining an AJAX action.
Definition: AjaxAction.inc.php:21
ScheduledTaskHelper\checkFrequency
static checkFrequency($className, $frequency)
Definition: ScheduledTaskHelper.inc.php:89
DAO\getDataChangedEvent
static getDataChangedEvent($elementId=null, $parentElementId=null, $content='')
Definition: DAO.inc.php:647
PKPAcronPlugin\getName
getName()
Definition: PKPAcronPlugin.inc.php:65
PKPAcronPlugin\getActions
getActions($request, $actionArgs)
Definition: PKPAcronPlugin.inc.php:93
PKPAcronPlugin\getInstallSitePluginSettingsFile
getInstallSitePluginSettingsFile()
Definition: PKPAcronPlugin.inc.php:86
Plugin\getEnabled
getEnabled()
Definition: Plugin.inc.php:868
Plugin\getSetting
getSetting($contextId, $name)
Definition: Plugin.inc.php:473
Config\getVar
static getVar($section, $key, $default=null)
Definition: Config.inc.php:35
LinkAction
Base class defining an action that can be performed by the user in the user interface.
Definition: LinkAction.inc.php:22
PKPAcronPlugin\$_tasksToRun
$_tasksToRun
Definition: PKPAcronPlugin.inc.php:35
PKPAcronPlugin\isSitePlugin
isSitePlugin()
Definition: PKPAcronPlugin.inc.php:57
HookRegistry\getHooks
static & getHooks($hookName=null)
Definition: HookRegistry.inc.php:28
XMLParser
Generic class for parsing an XML document into a data structure.
Definition: XMLParser.inc.php:28
Plugin\getPluginPath
getPluginPath()
Definition: Plugin.inc.php:330
PKPAcronPlugin\_getTasksToRun
_getTasksToRun()
Definition: PKPAcronPlugin.inc.php:335
Plugin\$request
$request
Definition: Plugin.inc.php:68
PKPAcronPlugin\getDescription
getDescription()
Definition: PKPAcronPlugin.inc.php:79
Plugin\addLocaleData
addLocaleData($locale=null)
Definition: Plugin.inc.php:454
XMLNode
Default handler for XMLParser returning a simple DOM-style object. This handler parses an XML documen...
Definition: XMLNode.inc.php:18
HookRegistry\register
static register($hookName, $callback, $hookSequence=HOOK_SEQUENCE_NORMAL)
Definition: HookRegistry.inc.php:70
PKPAcronPlugin
Removes dependency on 'cron' for scheduled tasks, including possible tasks defined by plugins....
Definition: PKPAcronPlugin.inc.php:23
NotificationManager
Definition: NotificationManager.inc.php:19
PKPApplication\get
static get()
Definition: PKPApplication.inc.php:235
PKPAcronPlugin\callbackManage
callbackManage($hookName, $args)
Definition: PKPAcronPlugin.inc.php:184
fatalError
if(!function_exists('import')) fatalError($reason)
Definition: functions.inc.php:32
HookRegistry\call
static call($hookName, $args=null)
Definition: HookRegistry.inc.php:86
ScheduledTaskHelper\getTaskArgs
static getTaskArgs($task)
Definition: ScheduledTaskHelper.inc.php:70
GenericPlugin
Abstract class for generic plugins.
Definition: GenericPlugin.inc.php:18
PKPAcronPlugin\manage
manage($args, $request)
Definition: PKPAcronPlugin.inc.php:114