['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; } }