['message' => 'Viewed %itemsName% list'],
'view' => ['message' => 'Viewed "%itemTitle%" %itemName%'],
'rowPreview' => 'view',
'new' => ['message' => 'Headed to create new %itemName%'],
'insert' => ['message' => 'Added new "%itemTitle%" %itemName%'],
'edit' => ['message' => 'Headed to edit "%itemTitle%" %itemName%'],
'update' => ['message' => 'Updated "%itemTitle%" %itemName%'],
'delete' => ['message' => 'Deleted "%itemTitle%" %itemName%'],
'reorder' => ['message' => 'Headed to reorder %itemsName%'],
'updateOrder' => ['message' => 'Updated %itemsName% order'],
'changeOption' => 'update', // mark 'changeOption' the same as 'update' action
'exportCsv' => ['message' => 'Exported %itemsName% to CSV'],
];
/** @var Qs_Db_Table */
protected $_table;
/** @var Qs_Db_Table */
protected $_sessionTable;
/**
* Role type. E.g.: admin, staff or user
* @var string
*/
protected $_role;
/**
* Role record identifier from appropriate table in database
* @var int
*/
protected $_roleId;
/**
* Role Session Identifier (PHPSESSID)
* @var string - 32 characters
*/
protected $_sessionId;
/**
* IP Address in format xxx.xxx.xxx.xxx
* @var string
*/
protected $_ip;
/** The name of Qs_ViewController class instance
* @var string
*/
protected $_controller;
/**
* The object's primary key
* @var int|null
*/
protected $_objectId;
/**
* Table alias for Qs_Db_Table class instance
* @var string
*/
protected $_tableAlias = 'ViewControllerLog';
/**
* List of controllers which logging ignored
*
* [
* 'App_Cms_View' => [], // ignore all actions
* 'App_News_View' => ['list', 'view'], // ignore certain actions
* ]
*
* @var array
*/
protected static $_ignoredControllers = [];
/**
* @param Zend_Config|array $options
*/
public function __construct($options)
{
Qs_Options::setConstructorOptions($this);
Qs_Options::setOptions($this, $options);
$this->_init();
}
protected function _init()
{
if (!isset($this->_role)) {
throw new Qs_ViewController_Exception('Role is undefined');
}
if (!isset($this->_roleId)) {
throw new Qs_ViewController_Exception('Role ID is undefined');
}
if (!isset($this->_sessionId)) {
throw new Qs_ViewController_Exception('Session ID is undefined');
}
if (!isset($this->_controller)) {
throw new Qs_ViewController_Exception('Controller class name is undefined');
}
$this->_applyActionMap();
$this->_table = new Qs_Db_Table($this->_tableAlias);
$this->_sessionTable = new Qs_Db_Table($this->_tableAlias . 'Session');
return $this;
}
protected function _applyActionMap()
{
foreach ($this->_actions as $action => $targetAction) {
if (is_string($targetAction)) {
if (!array_key_exists($targetAction, $this->_actions)) {
throw new Qs_ViewController_Exception('Undefined target action:' . $targetAction);
}
$this->_actions[$action] = $this->_actions[$targetAction];
}
}
return $this;
}
/**
* @param string $name
* @param string $message
* @return Qs_ViewController_Log
*/
public function setAction($name, $message)
{
$this->_actions[$name] = compact('message');
return $this;
}
/**
* @param string $action
* @return Qs_ViewController_Log
*/
public function removeAction($action)
{
if (array_key_exists($action, $this->_actions)) {
unset($this->_actions[$action]);
}
return $this;
}
public function setActions(array $actions)
{
foreach ($actions as $name => $message) {
$this->setAction($name, $message);
}
return $this;
}
/**
* @param null|string $field
* @param mixed $default
* @return mixed|Zend_Config
*/
public function getConfig($field = null, $default = null)
{
if (null === $this->_config) {
$this->_config = Qs_Config::getByInstance($this);
}
if (null === $field) {
return $this->_config;
}
return $this->_config->get($field, $default);
}
/**
* @param string $action
* @param array $options
* @return $this
*/
public function write($action, array $options = [])
{
if (!static::getEnabled()) {
return $this;
}
if (!array_key_exists($action, $this->_actions) || !$this->_role || !$this->_roleId) {
return $this;
}
if (false !== ($callback = $this->getActionCallback($action)) && false === call_user_func($callback)) {
return $this;
}
if (array_key_exists($this->_controller, static::$_ignoredControllers)) {
if (empty(static::$_ignoredControllers[$this->_controller])
|| in_array($action, static::$_ignoredControllers[$this->_controller])
) {
return $this;
}
}
$record = [
'role' => $this->_role,
'roleId' => $this->_roleId,
'sessionId' => $this->_sessionId,
'ip' => ip2long($this->_ip),
'controller' => $this->_controller,
'objectId' => $this->_objectId,
'action' => $action,
'message' => $this->_createMessage($action, $options),
];
if (!$this->_isDuplicate($record)) {
if (false === ($row = $this->_sessionTable->findRow($this->_sessionId))) {
$this->_sessionTable->insert([
'id' => $this->_sessionId,
'role' => $this->_role,
'roleId' => $this->_roleId,
]);
} else {
$row->setFromArray(['changed' => date('Y-m-d H:i:s'), 'closed' => 'n', 'closedAt' => null])->save();
}
$this->_table->insert($record);
}
return $this;
}
/**
* @param array $record
* @return bool
*/
protected function _isDuplicate(array $record)
{
if (false === ($lastRow = $this->_getLastRoleRecord())) {
return false;
}
$lastRow = array_intersect_key($lastRow, $record);
$diff = array_diff_assoc($lastRow, $record);
return empty($diff);
}
/**
* @return array
*/
protected function _getLastRoleRecord()
{
$filter = [
'role' => $this->_role,
'roleId' => $this->_roleId,
'sessionId' => $this->_sessionId,
];
return $this->_getLastRecord($filter);
}
/**
* @param array $filter
* @return array
*/
protected function _getLastRecord(array $filter = [])
{
$select = $this->_table->getAdapter()->select()->from(Qs_Db::getPair($this->_tableAlias));
Qs_Db::filter($select, $filter, $this->_tableAlias);
$select->order('id DESC');
$select->limit(1);
return $this->_table->getAdapter()->fetchRow($select);
}
/**
* @param string $action
* @param array $options
* @return string
*/
protected function _createMessage($action, array $options = [])
{
$message = $this->_actions[$action]['message'];
$matches = [];
preg_match_all('/%([^%]+)%/', $message, $matches);
$variables = $matches[1];
foreach ($variables as $variable) {
if (!array_key_exists($variable, $options)) {
$value = $this->getConfig($variable);
} else {
$value = $options[$variable];
}
$message = str_replace("%{$variable}%", $value, $message);
}
return $message;
}
/**
* @param string $controller
* @return Qs_ViewController_Log
*/
public function setController($controller)
{
$this->_controller = $controller;
return $this;
}
/**
* @return string
*/
public function getController()
{
return $this->_controller;
}
/**
* @param string $role
* @return Qs_ViewController_Log
*/
public function setRole($role)
{
$this->_role = $role;
return $this;
}
/**
* @return string
*/
public function getRole()
{
return $this->_role;
}
/**
* @param int $roleId
* @return Qs_ViewController_Log
*/
public function setRoleId($roleId)
{
$this->_roleId = (int) $roleId;
return $this;
}
/**
* @return int
*/
public function getRoleId()
{
return $this->_roleId;
}
/**
* @param string $roleSessionId
* @return Qs_ViewController_Log
*/
public function setSessionId($roleSessionId)
{
$this->_sessionId = $roleSessionId;
return $this;
}
/**
* @return string
*/
public function getSessionId()
{
return $this->_sessionId;
}
/**
* @param Zend_Config $config
* @return Qs_ViewController_Log
*/
public function setConfig(Zend_Config $config)
{
$this->_config = $config;
return $this;
}
/**
* @param string $ip
* @return Qs_ViewController_Log
*/
public function setIp($ip)
{
$this->_ip = $ip;
return $this;
}
/**
* @return string
*/
public function getIp()
{
return $this->_ip;
}
/**
* @return bool|int
*/
public function markSessionClosed()
{
return $this->_sessionTable->updateByKey(['closed' => 'y'], $this->_sessionId);
}
/**
* @param string $action
* @param callable $callback
* @return $this
* @throws Qs_ViewController_Exception
*/
public function setActionCallback($action, $callback)
{
if (!array_key_exists($action, $this->_actions)) {
throw new Qs_ViewController_Exception('Invalid action: ' . $action);
}
if (!is_callable($callback)) {
throw new Qs_ViewController_Exception('Invalid callback');
}
$this->_actions[$action]['callback'] = $callback;
return $this;
}
/**
* @param string $action
* @return callable|bool
*/
public function getActionCallback($action)
{
if (!array_key_exists($action, $this->_actions) || !array_key_exists('callback', $this->_actions[$action])) {
return false;
}
return $this->_actions[$action]['callback'];
}
/**
* @param int|null $objectId
* @return Qs_ViewController_Log
*/
public function setObjectId($objectId)
{
$this->_objectId = $objectId;
return $this;
}
/**
* @return int|null
*/
public function getObjectId()
{
return $this->_objectId;
}
/**
* Add controllers to ignore list
*
* $this->addIgnoredControllers('App_Cms_View'); // ignore all actions from App_Cms_View
* $this->addIgnoredControllers([
* 'App_Cms_View', // ignore all actions from App_Cms_View
* 'App_Gallery_Category_View' => '', // ignore all actions from App_Gallery_Category
* 'App_Gallery_View' => 'view', // ignore 'view; action from App_Gallery_Category
* 'App_News_View' => ['list', 'view'], // ignore 'list' and 'view' actions from App_News_View
* ]);
*
* @param array|string $controllers
* @return bool
* @throws Qs_Exception
*/
public static function addIgnoredControllers($controllers)
{
if (is_string($controllers)) {
$controllers = [$controllers => []];
} else if (is_array($controllers)) {
/** @var string|array $actions */
foreach ($controllers as $controller => $actions) {
if (is_numeric($controller)) {
if (is_string($actions)) {
$controllers[$actions] = [];
unset($controllers[$controller]);
} else {
throw new Qs_Exception('Wrong parameter type: ' . var_export([$controller => $actions], true));
}
}
if (is_string($controller)) {
if (is_string($actions) && !empty($actions)) {
$controllers[$controller] = [$actions];
} else if (!is_array($actions) || empty($actions)) {
$controllers[$controller] = [];
}
}
}
} else {
throw new Qs_Exception('Parameter type is wrong');
}
foreach ($controllers as $controller => $actions) {
if (array_key_exists($controller, static::$_ignoredControllers)) {
$actions = array_merge(static::$_ignoredControllers[$controller], $actions);
$actions = array_unique($actions);
}
static::$_ignoredControllers[$controller] = $actions;
}
return true;
}
/**
* @param string $controller
* @return bool
*/
public static function removeIgnoredController($controller)
{
if (array_key_exists($controller, static::$_ignoredControllers)) {
unset(static::$_ignoredControllers[$controller]);
}
return true;
}
public static function getIgnoredControllers()
{
return static::$_ignoredControllers;
}
public static function setEnabled($enabled)
{
static::$_enabled = (bool) $enabled;
}
public static function getEnabled()
{
if (null === static::$_enabled) {
static::$_enabled = Qs_Config::get('viewControllerLog', Qs_Config::QS_TYPE)->get('enabled', false);
}
return static::$_enabled;
}
}