PageRenderTime 60ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/concrete/src/Attribute/Key/SearchIndexer/StandardSearchIndexer.php

https://bitbucket.org/saltwaterdev/agilyx.com
PHP | 285 lines | 213 code | 32 blank | 40 comment | 39 complexity | 401c16163fa163845f43f7ec5fc2bdf6 MD5 | raw file
Possible License(s): LGPL-2.1, MPL-2.0-no-copyleft-exception, Apache-2.0, BSD-3-Clause
  1. <?php
  2. namespace Concrete\Core\Attribute\Key\SearchIndexer;
  3. use Concrete\Core\Attribute\AttributeKeyInterface;
  4. use Concrete\Core\Attribute\AttributeValueInterface;
  5. use Concrete\Core\Attribute\Category\CategoryInterface;
  6. use Concrete\Core\Attribute\Category\SearchIndexer\StandardSearchIndexerInterface;
  7. use Concrete\Core\Database\Connection\Connection;
  8. use Concrete\Core\Entity\Attribute\Key\Key;
  9. use Concrete\Core\Entity\Attribute\Value\Value;
  10. use Doctrine\DBAL\Platforms\MySqlPlatform;
  11. use Doctrine\DBAL\Schema\Comparator;
  12. use Doctrine\DBAL\Statement;
  13. use Doctrine\DBAL\Types\Type;
  14. class StandardSearchIndexer implements SearchIndexerInterface
  15. {
  16. protected $connection;
  17. protected $comparator;
  18. public function __construct(Connection $connection, Comparator $comparator)
  19. {
  20. $this->connection = $connection;
  21. $this->comparator = $comparator;
  22. }
  23. protected function getIndexEntryColumn(Key $key, $subKey = false)
  24. {
  25. if ($subKey) {
  26. $column = sprintf('ak_%s_%s', $key->getAttributeKeyHandle(), $subKey);
  27. } else {
  28. $column = sprintf('ak_%s', $key->getAttributeKeyHandle());
  29. }
  30. return $column;
  31. }
  32. /**
  33. * For certain fields (eg TEXT) Doctrine uses the length of the longest column to determine what field type to use.
  34. * For search indexing even if we may not currently have something long in a column,
  35. * we need the longest possible column so that we don't truncate any data.
  36. *
  37. * @param array $options
  38. *
  39. * @return array
  40. */
  41. private function setTypeLength($options)
  42. {
  43. // If we have explicitly set a length, use it
  44. if ($options['length']) {
  45. return $options;
  46. }
  47. if ($options['type']->getName() == 'text') {
  48. $options['length'] = MySqlPlatform::LENGTH_LIMIT_MEDIUMTEXT + 1; // This forces Doctrine to use `LONGTEXT` instead of `TINYTEXT`
  49. }
  50. return $options;
  51. }
  52. /**
  53. * Refresh the Search Index columns (if there are schema changes for example).
  54. *
  55. * @param CategoryInterface $category
  56. * @param AttributeKeyInterface $key
  57. */
  58. public function refreshSearchIndexKeyColumns(CategoryInterface $category, AttributeKeyInterface $key)
  59. {
  60. $controller = $key->getController();
  61. if ($key->isAttributeKeySearchable() == false ||
  62. $category->getIndexedSearchTable() == false ||
  63. $controller->getSearchIndexFieldDefinition() == false) {
  64. return false;
  65. }
  66. $definition = $controller->getSearchIndexFieldDefinition();
  67. $sm = $this->connection->getSchemaManager();
  68. $fromTable = $sm->listTableDetails($category->getIndexedSearchTable());
  69. $toTable = $sm->listTableDetails($category->getIndexedSearchTable());
  70. if (isset($definition['type'])) {
  71. $options = [
  72. 'type' => Type::getType($definition['type']),
  73. ];
  74. $options = array_merge($options, $definition['options']);
  75. $options = $this->setTypeLength($options);
  76. $toTable->changeColumn('ak_' . $key->getAttributeKeyHandle(), $options);
  77. } else {
  78. foreach ($definition as $name => $column) {
  79. $options = [
  80. 'type' => Type::getType($column['type']),
  81. ];
  82. $options = array_merge($options, $column['options']);
  83. $options = $this->setTypeLength($options);
  84. $toTable->changeColumn('ak_' . $key->getAttributeKeyHandle() . '_' . $name, $options);
  85. }
  86. }
  87. $comparator = $this->comparator;
  88. $diff = $comparator->diffTable($fromTable, $toTable);
  89. if ($diff !== false) {
  90. $sql = $this->connection->getDatabasePlatform()->getAlterTableSQL($diff);
  91. $arr = [];
  92. foreach ($sql as $q) {
  93. $arr[] = $q;
  94. $this->connection->exec($q);
  95. }
  96. }
  97. }
  98. /**
  99. * @param StandardSearchIndexerInterface $category
  100. * @param Key $key
  101. * @param $previousHandle
  102. */
  103. public function updateSearchIndexKeyColumns(CategoryInterface $category, AttributeKeyInterface $key, $previousHandle = null)
  104. {
  105. $controller = $key->getController();
  106. /*
  107. * Added this for some backward compatibility reason – but it's obviously not
  108. * right because it makes it so no search index columns get created.
  109. if (!$previousHandle) {
  110. $previousHandle = $key->getAttributeKeyHandle();
  111. }*/
  112. if ($key->getAttributeKeyHandle() == $previousHandle ||
  113. $key->isAttributeKeySearchable() == false ||
  114. $category->getIndexedSearchTable() == false ||
  115. $controller->getSearchIndexFieldDefinition() == false) {
  116. return false;
  117. }
  118. $fields = [];
  119. $dropColumns = [];
  120. $definition = $controller->getSearchIndexFieldDefinition();
  121. $sm = $this->connection->getSchemaManager();
  122. $toTable = $sm->listTableDetails($category->getIndexedSearchTable());
  123. if ($previousHandle) {
  124. if (isset($definition['type'])) {
  125. $dropColumns[] = 'ak_' . $previousHandle;
  126. } else {
  127. foreach ($definition as $name => $column) {
  128. $dropColumns[] = 'ak_' . $previousHandle . '_' . $name;
  129. }
  130. }
  131. }
  132. if (isset($definition['type'])) {
  133. if (!$toTable->hasColumn('ak_' . $key->getAttributeKeyHandle())) {
  134. $fields[] = [
  135. 'name' => 'ak_' . $key->getAttributeKeyHandle(),
  136. 'type' => $definition['type'],
  137. 'options' => $definition['options'],
  138. ];
  139. }
  140. } else {
  141. foreach ($definition as $name => $column) {
  142. if (!$toTable->hasColumn('ak_' . $key->getAttributeKeyHandle() . '_' . $name)) {
  143. $fields[] = [
  144. 'name' => 'ak_' . $key->getAttributeKeyHandle() . '_' . $name,
  145. 'type' => $column['type'],
  146. 'options' => $column['options'],
  147. ];
  148. }
  149. }
  150. }
  151. $fromTable = $sm->listTableDetails($category->getIndexedSearchTable());
  152. $parser = new \Concrete\Core\Database\Schema\Parser\ArrayParser();
  153. $comparator = $this->comparator;
  154. if ($previousHandle != false) {
  155. foreach ($dropColumns as $column) {
  156. $toTable->dropColumn($column);
  157. }
  158. }
  159. $toTable = $parser->addColumns($toTable, $fields);
  160. $diff = $comparator->diffTable($fromTable, $toTable);
  161. if ($diff !== false) {
  162. $sql = $this->connection->getDatabasePlatform()->getAlterTableSQL($diff);
  163. $arr = [];
  164. foreach ($sql as $q) {
  165. $arr[] = $q;
  166. $this->connection->exec($q);
  167. }
  168. }
  169. }
  170. /**
  171. * @param StandardSearchIndexerInterface $category
  172. * @param Value $value
  173. * @param mixed $subject
  174. */
  175. public function clearIndexEntry(CategoryInterface $category, AttributeValueInterface $value, $subject)
  176. {
  177. $key = $value->getAttributeKey();
  178. if (!$key->isAttributeKeySearchable()) {
  179. return false; // if it's not searchable there won't be the right columns in the database
  180. }
  181. $definition = $key->getController()->getSearchIndexFieldDefinition();
  182. if (!$definition) {
  183. return false;
  184. }
  185. $details = $category->getSearchIndexFieldDefinition();
  186. $primary = $details['primary'][0];
  187. $primaryValue = $category->getIndexedSearchPrimaryKeyValue($subject);
  188. $columnValues = [];
  189. if (isset($definition['type'])) {
  190. $col = $this->getIndexEntryColumn($key);
  191. $columnValues[$col] = null;
  192. } else {
  193. $subkeys = array_keys($definition);
  194. foreach ($subkeys as $subkey) {
  195. $col = $this->getIndexEntryColumn($key, $subkey);
  196. $columnValues[$col] = null;
  197. }
  198. }
  199. if (count($columnValues)) {
  200. $primaries = [$primary => $primaryValue];
  201. $this->connection->update(
  202. $category->getIndexedSearchTable(),
  203. $columnValues,
  204. $primaries
  205. );
  206. }
  207. }
  208. /**
  209. * @param StandardSearchIndexerInterface $category
  210. * @param Value $value
  211. * @param mixed $subject
  212. */
  213. public function indexEntry(CategoryInterface $category, AttributeValueInterface $value, $subject)
  214. {
  215. $columns = $this->connection->getSchemaManager()->listTableColumns($category->getIndexedSearchTable());
  216. $attributeValue = $value->getSearchIndexValue();
  217. $details = $category->getSearchIndexFieldDefinition();
  218. $primary = $details['primary'][0];
  219. $primaryValue = $category->getIndexedSearchPrimaryKeyValue($subject);
  220. $columnValues = [];
  221. /**
  222. * @var Statement
  223. */
  224. $exists = $this->connection->query(
  225. "select count({$primary}) from {$category->getIndexedSearchTable()} where {$primary} = {$primaryValue}"
  226. )->fetchColumn();
  227. if (is_array($attributeValue)) {
  228. foreach ($attributeValue as $valueKey => $valueValue) {
  229. $col = $this->getIndexEntryColumn($value->getAttributeKey(), $valueKey);
  230. if (isset($columns[strtolower($col)])) {
  231. $columnValues[$col] = $valueValue;
  232. }
  233. }
  234. } else {
  235. $col = $this->getIndexEntryColumn($value->getAttributeKey());
  236. if (isset($columns[strtolower($col)])) {
  237. $columnValues[$col] = $attributeValue;
  238. }
  239. }
  240. if (count($columnValues)) {
  241. $primaries = [$primary => $primaryValue];
  242. if ($exists) {
  243. $this->connection->update(
  244. $category->getIndexedSearchTable(),
  245. $columnValues,
  246. $primaries
  247. );
  248. } else {
  249. $this->connection->insert($category->getIndexedSearchTable(), $primaries + $columnValues);
  250. }
  251. }
  252. }
  253. }