PrivateSettingsTable.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. <?php
  2. namespace Elgg\Database;
  3. use Elgg\Database;
  4. use Elgg\Database\EntityTable;
  5. /**
  6. * Private settings for entities
  7. *
  8. * Private settings provide metadata like storage of settings for plugins
  9. * and users.
  10. *
  11. * WARNING: API IN FLUX. DO NOT USE DIRECTLY.
  12. *
  13. * @access private
  14. * @since 2.0.0
  15. */
  16. class PrivateSettingsTable {
  17. /** @var Database */
  18. private $db;
  19. /** @var EntityTable */
  20. private $entities;
  21. /** @var Name of the database table */
  22. private $table;
  23. /**
  24. * Constructor
  25. *
  26. * @param Database $db The database
  27. * @param EntityTable $entities Entities table
  28. */
  29. public function __construct(Database $db, EntityTable $entities) {
  30. $this->db = $db;
  31. $this->entities = $entities;
  32. $this->table = $this->db->getTablePrefix() . 'private_settings';
  33. }
  34. /**
  35. * Returns entities based upon private settings
  36. *
  37. * Also accepts all options available to elgg_get_entities(). Supports
  38. * the singular option shortcut.
  39. *
  40. * @param array $options Array in format:
  41. *
  42. * private_setting_names => null|ARR private setting names
  43. *
  44. * private_setting_values => null|ARR metadata values
  45. *
  46. * private_setting_name_value_pairs => null|ARR (
  47. * name => 'name',
  48. * value => 'value',
  49. * 'operand' => '=',
  50. * )
  51. * Currently if multiple values are sent via
  52. * an array (value => array('value1', 'value2')
  53. * the pair's operand will be forced to "IN".
  54. *
  55. * private_setting_name_value_pairs_operator => null|STR The operator to
  56. * use for combining
  57. * (name = value) OPERATOR (name = value);
  58. * default AND
  59. *
  60. * private_setting_name_prefix => STR A prefix to apply to all private
  61. * settings. Used to namespace plugin user
  62. * settings or by plugins to namespace their
  63. * own settings.
  64. *
  65. * @return mixed int If count, int. If not count, array. false on errors.
  66. */
  67. public function getEntities(array $options = array()) {
  68. $defaults = array(
  69. 'private_setting_names' => ELGG_ENTITIES_ANY_VALUE,
  70. 'private_setting_values' => ELGG_ENTITIES_ANY_VALUE,
  71. 'private_setting_name_value_pairs' => ELGG_ENTITIES_ANY_VALUE,
  72. 'private_setting_name_value_pairs_operator' => 'AND',
  73. 'private_setting_name_prefix' => '',
  74. );
  75. $options = array_merge($defaults, $options);
  76. $singulars = array(
  77. 'private_setting_name',
  78. 'private_setting_value',
  79. 'private_setting_name_value_pair',
  80. );
  81. $options = _elgg_normalize_plural_options_array($options, $singulars);
  82. $clauses = $this->getWhereSql('e',
  83. $options['private_setting_names'],
  84. $options['private_setting_values'],
  85. $options['private_setting_name_value_pairs'],
  86. $options['private_setting_name_value_pairs_operator'],
  87. $options['private_setting_name_prefix']);
  88. if ($clauses) {
  89. // merge wheres to pass to get_entities()
  90. if (isset($options['wheres']) && !is_array($options['wheres'])) {
  91. $options['wheres'] = array($options['wheres']);
  92. } elseif (!isset($options['wheres'])) {
  93. $options['wheres'] = array();
  94. }
  95. $options['wheres'] = array_merge($options['wheres'], $clauses['wheres']);
  96. // merge joins to pass to get_entities()
  97. if (isset($options['joins']) && !is_array($options['joins'])) {
  98. $options['joins'] = array($options['joins']);
  99. } elseif (!isset($options['joins'])) {
  100. $options['joins'] = array();
  101. }
  102. $options['joins'] = array_merge($options['joins'], $clauses['joins']);
  103. }
  104. return $this->entities->getEntities($options);
  105. }
  106. /**
  107. * Returns private setting name and value SQL where/join clauses for entities
  108. *
  109. * @param string $table Entities table name
  110. * @param array|null $names Array of names
  111. * @param array|null $values Array of values
  112. * @param array|null $pairs Array of names / values / operands
  113. * @param string $pair_operator Operator for joining pairs where clauses
  114. * @param string $name_prefix A string to prefix all names with
  115. * @return array
  116. */
  117. private function getWhereSql($table, $names = null, $values = null,
  118. $pairs = null, $pair_operator = 'AND', $name_prefix = '') {
  119. // @todo short circuit test
  120. $return = array (
  121. 'joins' => array (),
  122. 'wheres' => array(),
  123. );
  124. $return['joins'][] = "JOIN {$this->table} ps on
  125. {$table}.guid = ps.entity_guid";
  126. $wheres = array();
  127. // get names wheres
  128. $names_where = '';
  129. if ($names !== null) {
  130. if (!is_array($names)) {
  131. $names = array($names);
  132. }
  133. $sanitised_names = array();
  134. foreach ($names as $name) {
  135. $name = $name_prefix . $name;
  136. $sanitised_names[] = '\'' . $this->db->sanitizeString($name) . '\'';
  137. }
  138. $names_str = implode(',', $sanitised_names);
  139. if ($names_str) {
  140. $names_where = "(ps.name IN ($names_str))";
  141. }
  142. }
  143. // get values wheres
  144. $values_where = '';
  145. if ($values !== null) {
  146. if (!is_array($values)) {
  147. $values = array($values);
  148. }
  149. $sanitised_values = array();
  150. foreach ($values as $value) {
  151. // normalize to 0
  152. if (!$value) {
  153. $value = 0;
  154. }
  155. $sanitised_values[] = '\'' . $this->db->sanitizeString($value) . '\'';
  156. }
  157. $values_str = implode(',', $sanitised_values);
  158. if ($values_str) {
  159. $values_where = "(ps.value IN ($values_str))";
  160. }
  161. }
  162. if ($names_where && $values_where) {
  163. $wheres[] = "($names_where AND $values_where)";
  164. } elseif ($names_where) {
  165. $wheres[] = "($names_where)";
  166. } elseif ($values_where) {
  167. $wheres[] = "($values_where)";
  168. }
  169. // add pairs which must be in arrays.
  170. if (is_array($pairs)) {
  171. // join counter for incremental joins in pairs
  172. $i = 1;
  173. // check if this is an array of pairs or just a single pair.
  174. if (isset($pairs['name']) || isset($pairs['value'])) {
  175. $pairs = array($pairs);
  176. }
  177. $pair_wheres = array();
  178. foreach ($pairs as $index => $pair) {
  179. // @todo move this elsewhere?
  180. // support shortcut 'n' => 'v' method.
  181. if (!is_array($pair)) {
  182. $pair = array(
  183. 'name' => $index,
  184. 'value' => $pair
  185. );
  186. }
  187. // must have at least a name and value
  188. if (!isset($pair['name']) || !isset($pair['value'])) {
  189. // @todo should probably return false.
  190. continue;
  191. }
  192. if (isset($pair['operand'])) {
  193. $operand = $this->db->sanitizeString($pair['operand']);
  194. } else {
  195. $operand = ' = ';
  196. }
  197. // for comparing
  198. $trimmed_operand = trim(strtolower($operand));
  199. // if the value is an int, don't quote it because str '15' < str '5'
  200. // if the operand is IN don't quote it because quoting should be done already.
  201. if (is_numeric($pair['value'])) {
  202. $value = $this->db->sanitizeString($pair['value']);
  203. } else if (is_array($pair['value'])) {
  204. $values_array = array();
  205. foreach ($pair['value'] as $pair_value) {
  206. if (is_numeric($pair_value)) {
  207. $values_array[] = $this->db->sanitizeString($pair_value);
  208. } else {
  209. $values_array[] = "'" . $this->db->sanitizeString($pair_value) . "'";
  210. }
  211. }
  212. if ($values_array) {
  213. $value = '(' . implode(', ', $values_array) . ')';
  214. }
  215. // @todo allow support for non IN operands with array of values.
  216. // will have to do more silly joins.
  217. $operand = 'IN';
  218. } else if ($trimmed_operand == 'in') {
  219. $value = "({$pair['value']})";
  220. } else {
  221. $value = "'" . $this->db->sanitizeString($pair['value']) . "'";
  222. }
  223. $name = $this->db->sanitizeString($name_prefix . $pair['name']);
  224. // @todo The multiple joins are only needed when the operator is AND
  225. $return['joins'][] = "JOIN {$this->table} ps{$i}
  226. on {$table}.guid = ps{$i}.entity_guid";
  227. $pair_wheres[] = "(ps{$i}.name = '$name' AND ps{$i}.value
  228. $operand $value)";
  229. $i++;
  230. }
  231. $where = implode(" $pair_operator ", $pair_wheres);
  232. if ($where) {
  233. $wheres[] = "($where)";
  234. }
  235. }
  236. $where = implode(' AND ', $wheres);
  237. if ($where) {
  238. $return['wheres'][] = "($where)";
  239. }
  240. return $return;
  241. }
  242. /**
  243. * Gets a private setting for an entity
  244. *
  245. * Plugin authors can set private data on entities. By default private
  246. * data will not be searched or exported.
  247. *
  248. * @param int $entity_guid The entity GUID
  249. * @param string $name The name of the setting
  250. *
  251. * @return mixed The setting value, or null if does not exist
  252. */
  253. public function get($entity_guid, $name) {
  254. $entity_guid = (int) $entity_guid;
  255. $name = $this->db->sanitizeString($name);
  256. $entity = $this->entities->get($entity_guid);
  257. if (!$entity instanceof \ElggEntity) {
  258. return null;
  259. }
  260. $query = "SELECT value FROM {$this->table}
  261. where name = '{$name}' and entity_guid = {$entity_guid}";
  262. $setting = $this->db->getDataRow($query);
  263. if ($setting) {
  264. return $setting->value;
  265. }
  266. return null;
  267. }
  268. /**
  269. * Return an array of all private settings.
  270. *
  271. * @param int $entity_guid The entity GUID
  272. *
  273. * @return string[] empty array if no settings
  274. */
  275. function getAll($entity_guid) {
  276. $entity_guid = (int) $entity_guid;
  277. $entity = $this->entities->get($entity_guid);
  278. if (!$entity instanceof \ElggEntity) {
  279. return false;
  280. }
  281. $query = "SELECT * FROM {$this->table} WHERE entity_guid = {$entity_guid}";
  282. $result = $this->db->getData($query);
  283. if ($result) {
  284. $return = array();
  285. foreach ($result as $r) {
  286. $return[$r->name] = $r->value;
  287. }
  288. return $return;
  289. }
  290. return array();
  291. }
  292. /**
  293. * Sets a private setting for an entity.
  294. *
  295. * @param int $entity_guid The entity GUID
  296. * @param string $name The name of the setting
  297. * @param string $value The value of the setting
  298. * @return bool
  299. */
  300. public function set($entity_guid, $name, $value) {
  301. $entity_guid = (int) $entity_guid;
  302. $name = $this->db->sanitizeString($name);
  303. $value = $this->db->sanitizeString($value);
  304. $result = $this->db->insertData("INSERT into {$this->table}
  305. (entity_guid, name, value) VALUES
  306. ($entity_guid, '$name', '$value')
  307. ON DUPLICATE KEY UPDATE value='$value'");
  308. return $result !== false;
  309. }
  310. /**
  311. * Deletes a private setting for an entity.
  312. *
  313. * @param int $entity_guid The Entity GUID
  314. * @param string $name The name of the setting
  315. * @return bool
  316. */
  317. function remove($entity_guid, $name) {
  318. $entity_guid = (int) $entity_guid;
  319. $entity = $this->entities->get($entity_guid);
  320. if (!$entity instanceof \ElggEntity) {
  321. return false;
  322. }
  323. $name = $this->db->sanitizeString($name);
  324. return $this->db->deleteData("DELETE FROM {$this->table}
  325. WHERE name = '{$name}'
  326. AND entity_guid = {$entity_guid}");
  327. }
  328. /**
  329. * Deletes all private settings for an entity
  330. *
  331. * @param int $entity_guid The Entity GUID
  332. * @return bool
  333. */
  334. function removeAllForEntity($entity_guid) {
  335. $entity_guid = (int) $entity_guid;
  336. $entity = $this->entities->get($entity_guid);
  337. if (!$entity instanceof \ElggEntity) {
  338. return false;
  339. }
  340. return $this->db->deleteData("DELETE FROM {$this->table}
  341. WHERE entity_guid = {$entity_guid}");
  342. }
  343. }