'dirtyData') // дані візьмуться з поля 'dirtyData', обробляться і запишуться в поле 'rawData' * 2) array('dirtyData') // дані візьмуться з поля 'dirtyData', обробляться і запишуться в поле 'rawDirtyData' * @var array */ protected $_rawDataFields = []; /** * допустимі формати: * 1) array('field-1', 'field-2', ...) * 2) array('field-1', 'subForm-1' => array('field-1', 'field-2', ...), ...) * @var array */ protected $_nullableFields = []; protected $_fileFields = []; protected $_dependedFiles = []; protected $_options = []; protected $_filter = []; protected $_filterFields = []; protected $_filterExcludeFields = ['query', '__idItem']; protected $_filterAllowedEmptyFields = []; protected $_hasFilter = true; protected $_insertPlacement = 'PREPEND'; protected $_linkInsertPlacement; protected $_reorderKeyColumn = 'id'; protected $_reorderTitleColumn = 'title'; protected $_reorderIsGroupedItems = false; protected $_reorderGroupKeyColumn = null; protected $_reorderGroupTitleColumn = null; protected $_reorderGroupSorterColumn = null; protected $_errors = []; protected $_htmlPlaceholdersFields = []; protected $_htmlPlaceholdersData = []; /** * if true, before delete the all foreign reference to current table * (with on delete restrict, on delete no action) will be checked. * Please, set it at config * @var null|bool */ protected $_checkForeignKeyDependencies = null; protected $_constraintDeleteRules = ['RESTRICT', 'NO ACTION']; /** * array for appropriates DB_Obj classes and tableAliases - is used for getForeignKeysLockItems() * Structure: * array( * 'tableAlias' => 'ItemClass', * 'News' => '\App_News_Obj', * 'Mail' => '\App\Newsletters\Obj', * ...... * ) * * @var array */ protected $_tableModulesConversion = []; /** * Масив повідомлень для перевизнаяення дефолтного self::MSG_HAS_DEPENDENCIES * Формат (аліаси в множині, s - вкінці): * array( * 'tableAlias1' => 'message 1', * 'tableAlias2' => 'message 2', * ... * ) * @var array */ protected $_foreignKeyLocksMessages = []; public function __construct($options = []) { Qs_Options::setConstructorOptions($this); $this->setOptions($options); $this->_db = Qs_Db::getInstance(); if (null === $this->_tableAlias) { throw new Qs_Db_Exception('_tableAlias is not defined'); } if (!$this->_tableName) { $this->_tableName = Qs_Db::getTableName($this->_tableAlias); } $this->_init(); } protected function _init() { } function __call($name, $args) { $matches = []; if (preg_match('/^get(.*)4Select$/', $name, $matches)) { array_unshift($args, $matches[1]); return call_user_func_array([$this, '_get4Select'], $args); } throw new Qs_Db_Exception('Call to undefined method: ' . get_class($this) . ':' . $name); } public function getReorderOptionsCount() { $select = $this->_getReorderSelect(); $adapter = new Zend_Paginator_Adapter_DbSelect($select); return (int) $adapter->count(); } protected function _quoteField($field, $tableAlias = null) { return $this->_db->quoteIdentifier($this->_getField($field, $tableAlias)); } protected function _getTableAlias() { return $this->_tableShortAlias ?: $this->_tableAlias; } protected function _getField($field, $tableAlias = null) { if (null === $tableAlias) { $tableAlias = $this->_getTableAlias(); } return $tableAlias . '.' . $field; } protected function _getSubPair($tableSubAlias, $alias = null) { if (null === $alias) { $alias = $this->_getTableAlias() . $tableSubAlias; } return $this->_getPair($this->_tableAlias . $tableSubAlias, $alias); } protected function _getPair($tableAlias = null, $alias = null) { if (null === $tableAlias) { $tableAlias = $this->_tableAlias; if (null === $alias) { $alias = $this->_getTableAlias(); } } if (null === $alias) { $alias = $tableAlias; } return Qs_Db::getPair($tableAlias, $alias); } public function setOptions($options) { if ($options instanceof Zend_Config) { /** @var $options Zend_Config */ $options = $options->toArray(); } if (isset($options['options'])) { unset($options['options']); } foreach ($options as $key => $value) { $method = 'set' . ucfirst($key); if (method_exists($this, $method)) { $this->$method($value); } else { $this->_options[$key] = $value; } } return $this; } public function setLanguage($language) { $this->_language = $language; return $this; } public function getLanguage() { if (null == $this->_language) { $this->_language = Qs_Constant::get('CURRENT_LANGUAGE'); } return $this->_language; } public function isDefaultLanguage() { return $this->_language === Qs_Constant::get('DEFAULT_LANGUAGE'); } public function setSelect(Zend_Db_Select $select) { $this->_select = $select; return $this; } protected function _getSelect() { if (null == $this->_select) { $this->_select = $this->_db->select(); } return $this->_select; } protected function _getSorter($insertPlacement = null, $tableAlias = null) { $insertPlacement = (null === $insertPlacement) ? $this->_insertPlacement : $insertPlacement; $tableAlias = (null === $tableAlias) ? $this->_tableAlias : $tableAlias; if ('PREPEND' == $insertPlacement) { $function = 'MIN'; $diff = -1; } else if ('APPEND' == $insertPlacement) { $function = 'MAX'; $diff = 1; } else { throw new Qs_Db_Exception('Unknown insert placement: ' . $insertPlacement); } $select = $this->_db->select(); $select->from($this->_getPair($tableAlias), "{$function}(`{$tableAlias}`.`sorter`)"); $this->_where4Sorter($select); $select->limit(1); return (null === ($sorter = (int) $this->_db->fetchOne($select))) ? 0 : ($sorter + $diff); } /** * @param null $alias * @return Qs_Db_Table */ protected function _getTable($alias = null) { if (null === $alias) { $alias = $this->_tableAlias; } if (!isset($this->_tables[$alias])) { $this->_tables[$alias] = new Qs_Db_Table(['alias' => $alias, 'db' => $this->_db]); } return $this->_tables[$alias]; } protected function _getSubTable($suffix) { return $this->_getTable($this->_tableAlias . $suffix); } protected function _getTableName($alias = null) { if (null === $alias) { $alias = $this->_tableAlias; } return Qs_Db::getTableName($alias); } protected function _getQuotedTableName($alias = null) { return $this->_db->quoteIdentifier($this->_getTableName($alias)); } protected function _setFromColumns(array $fromColumns) { $this->_fromColumns = $fromColumns; return $this; } protected function _getFromColumns() { return $this->_fromColumns; } public function setFilterFields(array $fields) { $this->_filterFields = $fields; return $this; } public function getFilterFields() { return $this->_filterFields; } protected function _where4Sorter(Zend_Db_Select $select) { return $this; } protected function _getWherePrimaryKey($alias = null) { return $this->_getTable()->prepareWhere( (array) $this->_primaryKey, (null === $alias) ? $this->_getTableAlias() : $alias ); } public function initData() { $this->_data = $this->_getFromDb($this->getPrimaryKey()); return $this; } public function initFromForm(array $data) { $this->_arrayParseUrl2Tag($data); $this->_prepareNullableFields($data); $this->_prepareRawData($data); $this->_data = $data; return $this; } protected function _prepareNullableFields(array &$data, $fields = null) { if (null === $fields) { $fields = $this->_nullableFields; } foreach ($fields as $name => $value) { if (is_array($value)) { if (array_key_exists($name, $data)) { $this->_prepareNullableFields($data[$name], $value); } } else { $name = $value; if (array_key_exists($name, $data) && empty($data[$name])) { $data[$name] = null; } } } return $this; } public static function pregBaseUrl2Tag($content, $url) { $secure = Qs_SiteMap::isSecurePage($url); if (true === $secure) { $newUrl = str_replace([BASE_URL_HTTP, BASE_URL_HTTPS], Qs_Db_Obj::TAG_BASE_URL_HTTPS, $url); } else if (false === $secure) { $newUrl = str_replace([BASE_URL_HTTP, BASE_URL_HTTPS], Qs_Db_Obj::TAG_BASE_URL_HTTP, $url); } else if (0 === strpos($url, 'https://')) { $newUrl = str_replace(BASE_URL_HTTPS, Qs_Db_Obj::TAG_BASE_URL_HTTPS, $url); } else { $newUrl = str_replace(BASE_URL_HTTP, Qs_Db_Obj::TAG_BASE_URL_HTTP, $url); } return str_replace($url, $newUrl, $content); } protected function _baseUrl2Tag($content) { // base url $content = preg_replace_callback( '/\<(?:a|area)[^\>"\']+href\s*=\s*(?:"|\')(https?:\/\/[^"\']+)(?:"|\')(?:[^\>]+)?\>/ui', function ($matches) { return Qs_Db_Obj::pregBaseUrl2Tag(stripslashes($matches[0]), stripslashes($matches[1])); }, $content ); $content = str_replace(BASE_URL, Qs_Db_Obj::TAG_BASE_URL, $content); // anchors $pattern = '/]+)?href\\s*=\\s*([\'"])(#[\\w-]+)\\2/'; $replacement = '_urlParseFields; } if (empty($fields) || !is_array($array) || empty($array)) { return $this; } foreach ($fields as $field) { if (isset($array[$field])) { $array[$field] = $this->_tag2BaseUrl($array[$field]); } } return $this; } protected function _arrayParseUrl2Tag(&$array) { if (empty($this->_urlParseFields) || !is_array($array) || empty($array)) { return $this; } foreach ($this->_urlParseFields as $field) { if (isset($array[$field])) { $array[$field] = $this->_baseUrl2Tag($array[$field]); } } return $this; } /** * @param array $data * @return Qs_Db_Obj */ protected function _prepareRawData(array &$data) { if (is_array($this->_rawDataFields) && $this->_rawDataFields) { foreach ($this->_rawDataFields as $rawDataField => $dirtyDataField) { if (!$rawDataField || is_numeric($rawDataField)) { $rawDataField = static::RAW_FIELD_PREFIX . ucfirst($dirtyDataField); } $data[$rawDataField] = Qs_Text_Mark::prepareSearchText($data, $dirtyDataField); } } return $this; } protected function _prepareList(&$list) { foreach (array_keys($list) as $key) { $this->_prepareRow($list[$key]); } return $this; } protected function _prepareRow(array &$row) { $this->_arrayParseTag2Url($row); return $this; } public function insert(array $data = []) { if (empty($data)) { $data = $this->_data; } else { $this->_data = $data; } unset($data['id']); $metaData = $this->_getTable()->getMetaData(); if (isset($metaData['sorter'])) { $data['sorter'] = $this->_getSorter(); } $this->_db->beginTransaction(); $this->_clearErrors(); try { $this->_primaryKey = $this->_getTable()->insert($data); $this->_insertDependency(); $this->_db->commit(); } catch (Exception $e) { $this->_deleteFiles($data); $this->_db->rollBack(); Qs_Debug::log($e->getMessage(), 3); $this->_addError($e->getMessage()); return false; } $this->_handleFiles(); return $this->_primaryKey; } protected function _insertDependency() { return $this; } public function update(array $data = []) { if (empty($data)) { $data = $this->_data; } else { $this->_data = $data; } unset($data['id']); $this->_clearErrors(); $this->_db->beginTransaction(); try { if ($this instanceof PreviousDataInterface) { $this->loadPreviousData(); } $result = $this->_getTable()->updateByKey($data, $this->_primaryKey); $this->_updateDependency(); $this->_db->commit(); } catch (Exception $e) { $this->_db->rollBack(); Qs_Debug::log($e->getMessage(), 3); $this->_addError($e->getMessage()); return false; } $this->_handleFiles(); return $result; } protected function _updateDependency() { return $this; } protected function _updateIds($tableAlias, $referenceField, $fieldName, $fieldValues) { $this->_deleteIds($tableAlias, $referenceField); if (!$this->_primaryKey || !is_array($fieldValues) || empty($fieldValues)) { return false; } if (is_array($this->_primaryKey)) { throw new Qs_Db_Obj_Exception(__METHOD__ . ' does not support complex primary keys'); } $fields = [$referenceField, $fieldName]; $valueParts = []; foreach ($fieldValues as $referenceId) { $values = [$this->_primaryKey, $referenceId]; $values = array_map([$this->_db, 'quote'], $values); $valueParts[] = '(' . implode(', ', $values) . ')'; } $sql = 'INSERT INTO ' . $this->_getQuotedTableName($tableAlias) . ' (`' . implode('`, `', $fields) . '`) VALUES ' . PHP_EOL . implode(', ' . PHP_EOL, $valueParts) . ';'; return $this->_db->query($sql)->rowCount(); } protected function _deleteIds($tableAlias, $referenceField) { if (!$this->_primaryKey) { return false; } if (is_array($this->_primaryKey)) { throw new Qs_Db_Obj_Exception(__METHOD__ . ' does not support complex primary keys'); } $sql = 'DELETE FROM ' . $this->_getQuotedTableName($tableAlias) . ' ' . 'WHERE ' . $this->_db->quoteIdentifier($referenceField) . ' = ' . $this->_db->quote($this->_primaryKey, Qs_Db::INT_TYPE); return $this->_db->query($sql)->rowCount(); } protected function _handleFiles() { $session = new Qs_Session_Namespace(CURRENT_PAGE); if (isset($session->files) && !empty($session->files)) { $adapter = new Qs_File_Transfer_Adapter_Db(); foreach ($session->files as $file => $name) { $adapter->delete($file, $name); } unset($session->files); } return $this; } protected function removeFileFromSession($file) { $session = new Qs_Session_Namespace(CURRENT_PAGE); if (isset($session->files) && !empty($session->files)) { unset($session->files[$file]); } return $this; } public function updateOrder(array $order) { foreach ($order as $sorter => $id) { $this->_getTable()->updateByKey(['sorter' => (int) $sorter], $id); } return $this; } public function getFileFields() { return $this->_fileFields; } public function setFileFields($fields) { $this->_fileFields = $fields; } protected function _deleteFiles($row = null) { $adapter = new Qs_File_Transfer_Adapter_Db(); if (is_array($this->_fileFields) && !empty($this->_fileFields)) { if (null === $row) { if (false !== ($row = $this->_getTable()->findRow($this->getPrimaryKey()))) { $row = $row->toArray(); } } if (is_array($row) && !empty($row)) { foreach ($this->_fileFields as $name) { if (isset($row[$name]) && !empty($row[$name])) { $adapter->delete($row[$name], $name); } } } } foreach ($this->_dependedFiles as $file) { $adapter->deleteFromDefaultDestination($file); } return $this; } /** * @return int|bool Affected Rows */ public function delete() { $this->_clearErrors(); if (null === ($row = $this->getData())) { $this->_addError(static::MSG_INVALID_RECORD_ID); return false; } if ($this->getCheckForeignKeyDependencies()) { $foreignKeyLocks = $this->getForeignKeysLockItems(); if ($foreignKeyLocks) { $this->_addError($this->_getForeignKeyLocksMessages($foreignKeyLocks)); return false; } } $this->_beforeDelete(); $this->_db->beginTransaction(); try { $this->_deleteDependency(); $result = $this->_getTable()->deleteByKey($this->_primaryKey); $this->_db->commit(); $this->_afterDeleteSuccess(); } catch (Exception $e) { $this->_db->rollBack(); $this->_afterDeleteFail(); Qs_Debug::log($e->getMessage(), 3); $this->_addError($e->getMessage()); return false; } $this->_deleteFiles($row); return $result; } protected function _beforeDelete() { return $this; } protected function _afterDeleteSuccess() { return $this; } protected function _afterDeleteFail() { return $this; } protected function _deleteDependency() { return $this; } public function clearData() { $this->_data = null; return $this; } public function clearPrimaryKey() { $this->_primaryKey = null; return $this; } /** * @param bool $field * @param null $default * @return null|array */ public function getData($field = null, $default = null) { if (null === $this->_data && $this->getPrimaryKey() && $data = $this->_getFromDb($this->getPrimaryKey())) { $this->_data = $data; } if (null === $this->_data) { return null; } return Qs_Array::get($this->_data, $field, $default); } public function getObjectInfo() { $data = (array) $this->getData(); if (array_key_exists('title', $data)) { $data['itemTitle'] = $data['title']; } elseif (array_key_exists('name', $data)) { $data['itemTitle'] = $data['name']; } return $data; } protected function _getFromDbColumns() { return ['*']; } /** * @param $primaryKey * @return Zend_Db_Select */ protected function _getFromDbSelect($primaryKey) { $select = $this->_db->select(); $select->from($this->_getPair(), $this->_getFromDbColumns()); $select->where($this->_getTable()->prepareWhere((array) $primaryKey, $this->_getTableAlias())); return $select; } protected function _getFromDb($primaryKey, $field = null) { if (false === ($row = $this->_db->fetchRow($this->_getFromDbSelect($primaryKey)))) { return false; } if (null === $field) { $this->_addDependenciesFromDb($row); } $this->_prepareRow($row); return Qs_Array::get($row, $field); } protected function _addDependenciesFromDb(array &$data) { return $this; } protected function _getDependencySelect($dictionaryTableAlias, $fields, $dependedTableAlias, $linkField, array $key) { $select = $this->_db->select(); $where = ''; foreach ($key as $name => $value) { if (is_array($value)) { $where .= $this->_db->quoteInto("`t`.`{$name}` IN (?) ", $value) . ' '; } else { $where .= "`t`.`{$name}` = " . ((is_numeric($value)) ? intval($value) : $this->_db->quote($value)) . ' '; } } $where .= " AND `d`.`id` = `t`.`{$linkField}`"; $select->from($this->_getPair($dictionaryTableAlias, 'd'), $fields) ->join($this->_getPair($dependedTableAlias, 't'), $where, []); if ($this->_getTable($dependedTableAlias)->fieldExists('sorter')) { $select->order('t.sorter'); } elseif ($this->_getTable($dictionaryTableAlias)->fieldExists('sorter')) { $select->order('d.sorter'); } return $select; } protected function _prepareListDependency(array &$list, $listKey, $dictionaryTableAlias, $columns, $dependedTableAlias, $dictionaryLinkField, array $key) { foreach (array_keys($key) as $field) { array_unshift($columns, 't.' . $field); } $map = $this->_getDependenciesFrom($dictionaryTableAlias, $columns, $dependedTableAlias, $dictionaryLinkField, $key); foreach ($list as &$row) { $row[$listKey] = \Qs_Array::get($map, $row['id'], []); } return $this; } protected function _getDependenciesFrom($dictionaryTableAlias, $columns, $dependedTableAlias, $dictionaryLinkField, array $key) { $select = $this->_getDependencySelect( $dictionaryTableAlias, $columns, $dependedTableAlias, $dictionaryLinkField, $key ); $columnsCount = count($columns); if (1 == $columnsCount) { return $this->_db->fetchCol($select); } $groupBy = array_keys($key); $groupBy[] = Qs_Array::AUTOINCREMENT; return Qs_Array::group($this->_db->fetchAll($select), $groupBy); } /** * @return Zend_Db_Select */ public function getListSelect() { if (null === $this->_select) { $this->_select = $this->_db->select(); $this->_select->from($this->_getPair(), $this->_getFromColumns()); $this->_filter($this->_select); $this->_applySelectOptions($this->_select, $this->_selectOptions); } return $this->_select; } public function setSelectOptions(array $options = []) { $this->_selectOptions = $options; if (null !== $this->_select) { $this->_applySelectOptions($this->_select, $this->_selectOptions); } return $this; } protected function _applySelectOptions(Zend_Db_Select $select, array $options = []) { foreach ($options as $name => $value) { switch ($name) { case 'order': $select->reset(Zend_Db_Select::ORDER)->order($value); break; case 'limitPage': $select->reset(Zend_Db_Select::LIMIT_COUNT)->reset(Zend_Db_Select::LIMIT_OFFSET); call_user_func_array([$select, 'limitPage'], $value); break; default: Qs_Debug::log('Unsupported option name: ' . $name); break; } } return $this; } protected function _filter(Zend_Db_Select $select) { if (!$this->hasFilter()) { return $this; } $filterFields = $this->getFilterFields(); if (!empty($filterFields) && array_key_exists('query', $this->_filter)) { Qs_Db_Filter::where($select, $filterFields, $this->_filter['query']); } $filter = $this->_getCleanedFilter($this->_filter); $this->_getTable()->unsetUnknownFields($filter); Qs_Db::filter($select, $filter, $this->_getTableAlias()); return $this; } protected function _filterEnabled(Zend_Db_Select $select) { $select->where($this->_quoteField('enabled') . ' = ?', 'y'); return $this; } public function getListStatement() { return $this->getListSelect()->query(); } public function getListStatement4XmlSitemap($columns = ['alias']) { return $this->getListSelect()->reset(Zend_Db_Select::COLUMNS)->columns($columns)->query(); } public function getList() { $list = $this->getListStatement()->fetchAll(); $this->_prepareList($list); return $list; } public function getPrimary() { return $this->_getTable()->getPrimary(); } /** * @return int|array|null */ public function getPrimaryKey() { return $this->_primaryKey; } public function getPrimaryKeyArray() { $key = $this->getPrimaryKey(); if (null !== $key && !is_array($key)) { $primary = $this->getPrimary(); if (count($primary) != 1) { throw new Qs_Db_Obj_Exception('Primary key is wrong'); } $key = [reset($primary) => $key]; } return (array) $key; } public function getMetaData() { return $this->_getTable()->getMetaData(); } public function hasPrimaryKey() { return (null != $this->_primaryKey); } public function setPrimaryKeyByAlias($alias, $primary = 'id') { $this->setPrimaryKey($this->_getTable()->searchBy(compact('alias'), $primary)); return $this; } public function setPrimaryKey($_primaryKey) { if (empty($_primaryKey)) { return $this; } if (is_numeric($_primaryKey)) { $this->_primaryKey = intval($_primaryKey); return $this; } else if (is_string($_primaryKey)) { $this->_primaryKey = strval($_primaryKey); return $this; } foreach ($_primaryKey as $key => $value) { if (null === $value || '' === $value) { unset($_primaryKey[$key]); } } $metaData = $this->_getTable()->getMetaData(); foreach ($_primaryKey as $name => &$value) { switch ($metaData[$name]['DATA_TYPE']) { case 'int': $value = intval($value); break; case 'varchar': case 'char': $value = strval($value); break; default: break; } } if (!empty($_primaryKey)) { if (count($_primaryKey) > 1) { $this->_primaryKey = $_primaryKey; } else { $this->_primaryKey = reset($_primaryKey); } } return $this; } protected function _encryptPass($password, $salt = '') { return md5($salt . $password); } protected function _get4Select($tableAlias, $columns = '*', $where = null, $order = null, $limit = null) { return $this->_getTable($tableAlias)->get4Select($columns, $where, $order, $limit); } public function changeEnumOption($name) { $this->_clearErrors(); if (null === $this->getData()) { $this->_addError(static::MSG_INVALID_RECORD_ID); return false; } $meta = $this->getMetaData(); if (!array_key_exists($name, $meta)) { $this->_addError(static::MSG_INVALID_OPTION); return false; } $field = $meta[$name]; if (0 !== strncasecmp($field['DATA_TYPE'], 'enum', 4)) { $this->_addError(static::MSG_INVALID_OPTION); return false; } preg_match_all("/'([^']+)'/", $field['DATA_TYPE'], $matches); $values = $matches[1]; $value = $this->getData($name); if (false !== ($index = array_search($value, $values)) && array_key_exists($index + 1, $values)) { $newValue = $values[$index + 1]; } else { $newValue = (!empty($meta['DEFAULT'])) ? $meta['DEFAULT'] : $values[0]; } $this->_getTable()->updateByKey([$name => $newValue], $this->getPrimaryKey()); return true; } /** * @param string | array $field * @param mixed $value * @return Qs_Db_Obj */ public function setFilter($field, $value = null) { if (is_array($field)) { $this->_filter = $field; } else { $this->_filter[$field] = $value; } $this->_clearSelect(); return $this; } public function addFilter(array $filter) { $this->_filter = array_merge($this->_filter, $filter); $this->_clearSelect(); return $this; } protected function _clearSelect() { $this->_select = null; return $this; } /** * Remove from filter unused values * @param array $filter * @return array */ protected function _getCleanedFilter($filter) { $fields = array_keys($filter); $fields = array_diff($fields, $this->_filterAllowedEmptyFields); foreach ($fields as $name) { if (is_scalar($filter[$name])) { if ('' == trim($filter[$name])) { unset($filter[$name]); } } else { if (empty($filter[$name])) { unset($filter[$name]); } } } foreach ($this->_filterExcludeFields as $name) { unset($filter[$name]); } return $filter; } public function getFilter($field = null) { if (null === $field) { return $this->_filter; } return Qs_Array::get($this->_filter, $field); } public function hasFilter() { return $this->_hasFilter; } public function disableFilter() { $this->_hasFilter = false; return $this; } public function enableFilter() { $this->_hasFilter = true; return $this; } public function setReorderTitleColumn($name) { $this->_reorderTitleColumn = $name; return $this; } public function getReorderTitleColumn() { return $this->_reorderTitleColumn; } public function setReorderKeyColumn($name) { $this->_reorderKeyColumn = $name; return $this; } public function getReorderKeyColumn() { return $this->_reorderKeyColumn; } protected function _getReorderSelect() { $select = clone $this->getListSelect(); $this->_where4Sorter($select); if ($this->_reorderIsGroupedItems && $this->_reorderGroupSorterColumn) { $select->order($this->_reorderGroupSorterColumn); } $select->order($this->_getTableAlias() . '.sorter'); return $select; } public function getReorderGroupKeyColumn() { return $this->_reorderGroupKeyColumn; } public function getReorderGroupTitleColumn() { $titleColumn = $this->_reorderGroupTitleColumn; if (empty($titleColumn) && $this->hasReorderGroupedItems()) { $keyColumn = $this->getReorderGroupKeyColumn(); $keyColumnLength = strlen($keyColumn); if (substr($keyColumn, $keyColumnLength - 2) === 'Id') { $titleColumn = substr($keyColumn, 0, $keyColumnLength - 2); } else if (substr($keyColumn, 0, 2) === 'id') { $titleColumn = lcfirst(substr($keyColumn, 2, $keyColumnLength - 2)); } } return $titleColumn; } public function hasReorderGroupedItems() { return $this->_reorderIsGroupedItems; } public function getReorderOptions($keyColumn = null, $titleColumn = null, $reorderGroupKeyColumn = null) { $options = []; $keyColumn = (null === $keyColumn) ? $this->getReorderKeyColumn() : $keyColumn; $titleColumn = (null === $titleColumn) ? $this->getReorderTitleColumn() : $titleColumn; $list = $this->_db->fetchAll($this->_getReorderSelect()); $reorderGroupKeyColumn = (null === $reorderGroupKeyColumn) ? $this->getReorderGroupKeyColumn() : $reorderGroupKeyColumn; $reorderGroupTitleColumn = $this->getReorderGroupTitleColumn(); foreach ($list as $row) { if ($this->hasReorderGroupedItems() && !empty($reorderGroupKeyColumn)) { $reorderGroupTitle = (array_key_exists($reorderGroupTitleColumn, $row)) ? $row[$reorderGroupTitleColumn] : $row[$reorderGroupKeyColumn]; $options[$reorderGroupTitle][$row[$keyColumn]] = $row[$titleColumn]; } else { $options[$row[$keyColumn]] = $row[$titleColumn]; } } if ($this->hasReorderGroupedItems() && 1 == count($options)) { $options = reset($options); } return $options; } protected function _clearErrors() { $this->_errors = []; return $this; } protected function _addError($message) { if (is_string($message)) { $message = [$message]; } $this->_errors = array_merge($this->_errors, $message); return $this; } public function getErrors() { return $this->_errors; } /** * Converts array of ids in two dimensional array for Qs_Db_Obj->_insertLinks() * @param string $linkIdField * @param array $linkedIds * @param string $itemIdField * @param int $itemId * @param array|null $linkData [OPTIONAL] * @return array|null */ protected function _ids2Links($linkIdField, $linkedIds, $itemIdField, $itemId, array $linkData = null) { $links = []; if (is_array($linkedIds)) { foreach ($linkedIds as $id) { $row = [$itemIdField => $itemId, $linkIdField => $id]; $links[] = (null === $linkData) ? $row : array_merge($linkData, $row); } } return (empty($links)) ? null : $links; } /** * @throws Qs_Db_Obj_Exception * @param string $tableAlias * @param array $list Multi dimensional array with data for each link (overrides $linkData) * @param array $linkData Data that should be added to each link * @return int Affected rows count */ protected function _insertLinks($tableAlias, array $list = null, array $linkData = []) { if (empty($list)) { return 0; } $firstRow = reset($list); if (!is_array($list) || !is_array($firstRow)) { throw new Qs_Db_Obj_Exception('Param $list is in wrong format'); } /** @var $table Qs_Db_Table */ $table = $this->_getTable($tableAlias); $tableName = $this->_getTableName($tableAlias); $metaData = $table->info(Zend_Db_Table::METADATA); $linkData = (array) $linkData; // init date if (isset($metaData['added']) || isset($metaData['changed'])) { $linkData['added'] = $linkData['changed'] = date('Y-m-d H:i:s'); } // init sorter $initSorter = $insertPlacement = null; if (isset($metaData['sorter']) && !isset($firstRow['sorter'])) { if (isset($linkData['sorter'])) { $initSorter = (int) $linkData['sorter']; } else { $insertPlacement = (null === $this->_linkInsertPlacement) ? $this->_insertPlacement : $this->_linkInsertPlacement; $initSorter = $this->_getSorter($insertPlacement, $tableAlias); } $linkData['sorter'] = $initSorter; } $fields = array_keys(array_intersect_key($metaData, array_merge($linkData, $firstRow))); $sqlParts = []; foreach ($list as $row) { $_row = array_merge($linkData, $row); if (!isset($row['sorter']) && null !== $initSorter) { $_row['sorter'] = ('APPEND' === $insertPlacement) ? $initSorter++ : $initSorter--; } $_row = Qs_Array::map($_row, $fields); $_row = Qs_Db::quoteRow($_row, $table); if ($fields !== array_keys($_row)) { throw new Qs_Db_Obj_Exception('Inconsistent columns count'); } $sqlParts[] = '(' . implode(', ', $_row) . ')'; } $sql = 'INSERT INTO ' . $this->_db->quoteIdentifier($tableName) . ' (`' . implode('`, `', $fields) . '`) VALUES ' . PHP_EOL . implode(', ' . PHP_EOL, $sqlParts) . ';'; return $this->_db->query($sql)->rowCount(); } public function setResourceName($resourceName) { $this->_resourceName = $resourceName; return $this; } public function getResourceName() { if (null === $this->_resourceName) { if (null === ($this->_resourceName = $this->getConfig('resourceName'))) { throw new Qs_Db_Exception('Resource name is not defined'); } } return $this->_resourceName; } public function getObjectId() { return $this->getPrimaryKey(); } /** * Return paceholders with values for html blocks * * @return array */ public function getPlaceholdersData() { return $this->_getHmlPlaceholdersData(); } /** * Return placeholder fields for placeholders * * @return array */ public function getPlaceholders() { $placeholders = []; foreach ($this->_getHtmlPlaceholdersFields() as $field => $value) { if (is_int($field)) { $field = $value; $value = ucfirst(preg_replace('/([a-z])([A-Z])/', '$1 $2', $value)); } $field = '{' . $field . '}'; $placeholders[$field] = $value; } return $placeholders; } /** * Return data for placeholder replace * * @return array */ protected function _getHtmlPlaceholdersData() { return $this->_htmlPlaceholdersData; } /** * Set data for placeholred replace * * @param array $data * @return Qs_Db_Obj */ protected function _setHtmlPlaceholdersData($data) { $this->_htmlPlaceholdersData = $data; return $this; } /** * Prepare html placeholder - array example: array('placeholder' => 'placeholder title', ....) * * @return array */ protected function _getHmlPlaceholdersData() { $placeholdersData = []; $data = $this->_getHtmlPlaceholdersData(); if (!empty($data)) { foreach ($this->_getHtmlPlaceholdersFields() as $key => $field) { $index = (ctype_digit($key) || is_int($key)) ? $field : $key; $placeholder = '{' . $index . '}'; $placeholdersData[$placeholder] = Qs_Array::get($data, $index); } } return $placeholdersData; } protected function _setHtmlPlaceholdersFields(array $fields) { $this->_htmlPlaceholdersFields = $fields; return $this; } protected function _getHtmlPlaceholdersFields() { if (empty($this->_htmlPlaceholdersFields)) { $this->_htmlPlaceholdersFields = array_keys($this->_htmlPlaceholdersData); } return $this->_htmlPlaceholdersFields; } /** * @param array $providerOptions provider options from site/App/Search/config.php * @return array */ public function getSearchList(array $providerOptions) { $list = $this->getListStatement()->fetchAll(); $this->_prepareSearchList($list, $providerOptions); return $list; } /** * @param array $list items list * @param array $providerOptions provider options from site/App/Search/config.php * @return Qs_Db_Obj */ protected function _prepareSearchList(array &$list, array $providerOptions) { $this->_prepareList($list); if (!empty($list)) { $query = $this->_filter['query']; $sitemapUrl = ''; if (($selector = Qs_Array::get($providerOptions, 'sitemapSelector'))) { if (!is_array($selector)) { $selector = [$selector]; } else { $paramsCount = count($selector); if ($paramsCount < 3) { $selector = array_merge($selector, array_fill(0, (3 - $paramsCount), null)); } } $selector[] = 'url'; $sitemapUrl = call_user_func_array('Qs_SiteMap::findFirst', $selector); } foreach ($list as &$row) { $row['sitemapUrl'] = $sitemapUrl; $row['searchUrl'] = $this->_buildSearchUrl(Qs_Array::get($providerOptions, 'urlPattern'), $row); $uri = new Zend\Uri\Http(); $uri->parse($row['searchUrl']); if (($uriQuery = $uri->getQueryAsArray()) && array_key_exists('page', $uriQuery) && $uriQuery['page'] == '1') { unset($uriQuery['page']); $uri->setQuery($uriQuery); $row['searchUrl'] = $uri->__toString(); } $titleField = Qs_Array::get($providerOptions, 'titleField', App\Search\View::DEFAULT_TITLE_FIELD); $row['title'] = Qs_Text_Mark::markSearchWords($row[$titleField], $query); $contentFields = $this->getFilterFields(); $contentFields = Qs_Array::excludeValue($contentFields, $titleField); if (in_array('title', $contentFields)) { $contentFields = Qs_Array::excludeValue($contentFields, 'title'); } $content = Qs_Text_Mark::prepareSearchText($row, $contentFields, false); $row['content'] = Qs_Text_Mark::markSearchWords($content, $query); } } return $this; } /** * @param string|null $pattern * @param array $data * @return string */ protected function _buildSearchUrl($pattern, array $data) { $placeholders = array_keys($data); array_walk($placeholders, function (&$value, $key) { $value = '{' . $value . '}'; }); $values = array_values($data); array_walk($values, function (&$value, $key) { if (!is_scalar($value)) { $value = ''; } }); return str_replace($placeholders, $values, $pattern); } protected function _getForeignKeyLocksMessages($foreignKeyLocks) { $replacements = [ '%itemName%' => $this->getConfig()->itemName, '%name%' => $this->getData('name'), ]; $placeholders = array_keys($replacements); $messages = []; if (!empty($this->_foreignKeyLocksMessages)) { foreach ($foreignKeyLocks as $index => $foreignKey) { if (array_key_exists($foreignKey, $this->_foreignKeyLocksMessages)) { $message = $this->_foreignKeyLocksMessages[$foreignKey]; $messages[] = str_replace($placeholders, $replacements, $message); unset($foreignKeyLocks[$index]); } } } if (!empty($foreignKeyLocks)) { $replacements['%foreignKeyLocks%'] = implode(', ', $foreignKeyLocks); $placeholders = array_keys($replacements); $messages[] = str_replace($placeholders, $replacements, static::MSG_HAS_DEPENDENCIES); } return $messages; } /** * @return bool|null */ public function getCheckForeignKeyDependencies() { if (null == $this->_checkForeignKeyDependencies) { $this->_checkForeignKeyDependencies = (bool) $this->getConfig('checkForeignKeyDependencies'); } return $this->_checkForeignKeyDependencies; } /** * ATTENTION! use ONLY for projects with SINGLE DB. * * method return array of items (table aliases or appropriates from $this->_tableModulesConversion) * which have foreign keys referenced to current $_tableAlias * * @return array */ public function getForeignKeysLockItems() { return $this->_getLockItemsByRules($this->_getLockRulesForDeleteConstraints()); } /** * structure of rules see at \Qs_Db_Obj::_getLockRulesForDeleteConstraints() returns. * * @param array $rules * @return array */ protected function _getLockItemsByRules($rules) { if (!$rules) { return []; } $dependencies = []; $tablePrefix = \Qs_Db::getConfig()->database->tablePrefix; $this->_db->query('USE `' . \Qs_Db::getConfig()->database->params->dbname . '`'); foreach ($rules as $rule) { $referenceFieldValue = $this->getData($rule['referencedColumn']); $sql = " SELECT '1' FROM `{$rule['tableName']}` WHERE `{$rule['tableName']}`.`{$rule['column']}` = " . $this->_db->quote($referenceFieldValue) . " LIMIT 1 "; if ($this->_db->fetchOne($sql)) { $tableAlias = str_replace($tablePrefix, '', $rule['tableName']); $dependencies[] = $this->_getForeignKeyLockItemName($tableAlias); } } return $dependencies; } protected function _getForeignKeyLockItemName($tableAlias) { $itemsName = null; if (!empty($this->_tableModulesConversion[$tableAlias])) { /** @var string $className */ $className = $this->_tableModulesConversion[$tableAlias]; if (class_exists($className)) { /** @var \Qs_Db_Obj $itemObj */ $itemObj = new $className(); $itemsName = $itemObj->getConfig('itemsName'); } } if (!$itemsName) { $itemsName = \Qs_Translate::getPlural($tableAlias, 2); } return $itemsName; } /** * method is used for getting tables and columns that have foreign key reference to current Db_Obj table * * method return arrays of array of tableName, column and referenceColumn. * The referenceColumn (a column from current Db_Obj table) * is the foreign key reference for `tableName`.`column` * * Structure of returns: * array( * array( * 'tableName' => 'Table1', * 'column' => 'someId', * 'referenceColumn => 'id', * ), * array( * 'tableName' => 'Table2', * 'column' => 'someId', * 'referenceColumn => 'id', * ), * array( * 'tableName' => 'Table3', * 'column' => 'someFieldName', * 'referenceColumn => 'fieldName', * ), * .... * ) * * * @return array */ protected function _getLockRulesForDeleteConstraints() { if (!$this->_constraintDeleteRules) { return []; } return $this->_db->fetchAll($this->_getColumnUsageInformationSql()); } /** * method returns sql-string for getting tables and columns that have foreign key reference to current Db_Obj table * * * @return string */ protected function _getColumnUsageInformationSql() { $dbName = \Qs_Db::getConfig()->database->params->dbname; return " SELECT `TABLE_NAME` as `tableName`, `COLUMN_NAME` as `column`, `REFERENCED_COLUMN_NAME` as `referencedColumn` FROM `information_schema`.`KEY_COLUMN_USAGE` WHERE `TABLE_SCHEMA` = " . $this->_db->quote($dbName) . " AND `CONSTRAINT_NAME` IN (" . $this->_getConstraintSubSql() . "); "; } /** * method returns sql-string for getting constraints names (foreign keys names) * from `information_schema`.`REFERENTIAL_CONSTRAINTS` * for current project db, where reference table is current Db_Obj table * with specified foreign key delete rules * * @return string */ protected function _getConstraintSubSql() { $dbName = \Qs_Db::getConfig()->database->params->dbname; $quotedDbName = $this->_db->quote($dbName); $quotedTableName = $this->_db->quote(\Qs_Db::getConfig()->database->tablePrefix . $this->_tableAlias); return " SELECT `CONSTRAINT_NAME` FROM `information_schema`.`REFERENTIAL_CONSTRAINTS` as `rc` WHERE `rc`.`CONSTRAINT_SCHEMA` = " . $quotedDbName . " AND `rc`.`REFERENCED_TABLE_NAME` = " . $quotedTableName . " AND `rc`.`DELETE_RULE` IN (" . $this->_db->quote($this->_constraintDeleteRules) . ") "; } }