PageRenderTime 41ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/admin/tool/dataprivacy/classes/data_registry.php

https://github.com/mackensen/moodle
PHP | 355 lines | 188 code | 46 blank | 121 comment | 67 complexity | 0dab14d667f34f9c3cca20a8f538ac00 MD5 | raw file
  1. <?php
  2. // This file is part of Moodle - http://moodle.org/
  3. //
  4. // Moodle is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // Moodle is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  16. /**
  17. * Data registry business logic methods. Mostly internal stuff.
  18. *
  19. * All methods should be considered part of the internal tool_dataprivacy API
  20. * unless something different is specified.
  21. *
  22. * @package tool_dataprivacy
  23. * @copyright 2018 David Monllao
  24. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  25. */
  26. namespace tool_dataprivacy;
  27. use coding_exception;
  28. use core\persistent;
  29. defined('MOODLE_INTERNAL') || die();
  30. /**
  31. * Data registry business logic methods. Mostly internal stuff.
  32. *
  33. * @copyright 2018 David Monllao
  34. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  35. */
  36. class data_registry {
  37. /**
  38. * Returns purpose and category var names from a context class name
  39. *
  40. * @param string $classname The context level's class.
  41. * @param string $pluginname The name of the plugin associated with the context level.
  42. * @return string[]
  43. */
  44. public static function var_names_from_context($classname, $pluginname = '') {
  45. $pluginname = trim($pluginname);
  46. if (!empty($pluginname)) {
  47. $categoryvar = $classname . '_' . $pluginname . '_category';
  48. $purposevar = $classname . '_' . $pluginname . '_purpose';
  49. } else {
  50. $categoryvar = $classname . '_category';
  51. $purposevar = $classname . '_purpose';
  52. }
  53. return [
  54. $purposevar,
  55. $categoryvar
  56. ];
  57. }
  58. /**
  59. * Returns the default purpose id and category id for the provided context level.
  60. *
  61. * The caller code is responsible of checking that $contextlevel is an integer.
  62. *
  63. * @param int $contextlevel The context level.
  64. * @param string $pluginname The name of the plugin associated with the context level.
  65. * @return int[]|false[]
  66. */
  67. public static function get_defaults($contextlevel, $pluginname = '') {
  68. $classname = \context_helper::get_class_for_level($contextlevel);
  69. list($purposevar, $categoryvar) = self::var_names_from_context($classname, $pluginname);
  70. $purposeid = get_config('tool_dataprivacy', $purposevar);
  71. $categoryid = get_config('tool_dataprivacy', $categoryvar);
  72. if (!empty($pluginname)) {
  73. list($purposevar, $categoryvar) = self::var_names_from_context($classname);
  74. // If the plugin-level doesn't have a default purpose set, try the context level.
  75. if ($purposeid == false) {
  76. $purposeid = get_config('tool_dataprivacy', $purposevar);
  77. }
  78. // If the plugin-level doesn't have a default category set, try the context level.
  79. if ($categoryid == false) {
  80. $categoryid = get_config('tool_dataprivacy', $categoryvar);
  81. }
  82. }
  83. if (empty($purposeid)) {
  84. $purposeid = context_instance::NOTSET;
  85. }
  86. if (empty($categoryid)) {
  87. $categoryid = context_instance::NOTSET;
  88. }
  89. return [$purposeid, $categoryid];
  90. }
  91. /**
  92. * Are data registry defaults set?
  93. *
  94. * At least the system defaults need to be set.
  95. *
  96. * @return bool
  97. */
  98. public static function defaults_set() {
  99. list($purposeid, $categoryid) = self::get_defaults(CONTEXT_SYSTEM);
  100. if (empty($purposeid) || empty($categoryid)) {
  101. return false;
  102. }
  103. return true;
  104. }
  105. /**
  106. * Returns all site categories that are visible to the current user.
  107. *
  108. * @return \core_course_category[]
  109. */
  110. public static function get_site_categories() {
  111. global $DB;
  112. if (method_exists('\core_course_category', 'get_all')) {
  113. $categories = \core_course_category::get_all(['returnhidden' => true]);
  114. } else {
  115. // Fallback (to be removed once this gets integrated into master).
  116. $ids = $DB->get_fieldset_select('course_categories', 'id', '');
  117. $categories = \core_course_category::get_many($ids);
  118. }
  119. foreach ($categories as $key => $category) {
  120. if (!$category->is_uservisible()) {
  121. unset($categories[$key]);
  122. }
  123. }
  124. return $categories;
  125. }
  126. /**
  127. * Returns the roles assigned to the provided level.
  128. *
  129. * Important to note that it returns course-level assigned roles
  130. * if the provided context level is below course.
  131. *
  132. * @param \context $context
  133. * @return array
  134. */
  135. public static function get_subject_scope(\context $context) {
  136. if ($contextcourse = $context->get_course_context(false)) {
  137. // Below course level we look at module or block level roles + course-assigned roles.
  138. $courseroles = get_roles_used_in_context($contextcourse, false);
  139. $roles = $courseroles + get_roles_used_in_context($context, false);
  140. } else {
  141. // We list category + system for others (we don't work with user instances so no need to work about them).
  142. $roles = get_roles_used_in_context($context);
  143. }
  144. return array_map(function($role) {
  145. if ($role->name) {
  146. return $role->name;
  147. } else {
  148. return $role->shortname;
  149. }
  150. }, $roles);
  151. }
  152. /**
  153. * Returns the effective value given a context instance
  154. *
  155. * @param \context $context
  156. * @param string $element 'category' or 'purpose'
  157. * @param int|false $forcedvalue Use this value as if this was this context instance value.
  158. * @return persistent|false It return a 'purpose' instance or a 'category' instance, depending on $element
  159. */
  160. public static function get_effective_context_value(\context $context, $element, $forcedvalue = false) {
  161. global $DB;
  162. if ($element !== 'purpose' && $element !== 'category') {
  163. throw new coding_exception('Only \'purpose\' and \'category\' are supported.');
  164. }
  165. $fieldname = $element . 'id';
  166. if (!empty($forcedvalue) && ($forcedvalue == context_instance::INHERIT)) {
  167. // Do not include the current context when calculating the value.
  168. // This has the effect that an inheritted value is calculated.
  169. $parentcontextids = $context->get_parent_context_ids(false);
  170. } else if (!empty($forcedvalue) && ($forcedvalue != context_instance::NOTSET)) {
  171. return self::get_element_instance($element, $forcedvalue);
  172. } else {
  173. // Fetch all parent contexts, including self.
  174. $parentcontextids = $context->get_parent_context_ids(true);
  175. }
  176. list($insql, $inparams) = $DB->get_in_or_equal($parentcontextids, SQL_PARAMS_NAMED);
  177. $inparams['contextmodule'] = CONTEXT_MODULE;
  178. if ('purpose' === $element) {
  179. $elementjoin = 'LEFT JOIN {tool_dataprivacy_purpose} ele ON ctxins.purposeid = ele.id';
  180. $elementfields = purpose::get_sql_fields('ele', 'ele');
  181. } else {
  182. $elementjoin = 'LEFT JOIN {tool_dataprivacy_category} ele ON ctxins.categoryid = ele.id';
  183. $elementfields = category::get_sql_fields('ele', 'ele');
  184. }
  185. $contextfields = \context_helper::get_preload_record_columns_sql('ctx');
  186. $fields = implode(', ', ['ctx.id', 'm.name AS modname', $contextfields, $elementfields]);
  187. $sql = "SELECT $fields
  188. FROM {context} ctx
  189. LEFT JOIN {tool_dataprivacy_ctxinstance} ctxins ON ctx.id = ctxins.contextid
  190. LEFT JOIN {course_modules} cm ON ctx.contextlevel = :contextmodule AND ctx.instanceid = cm.id
  191. LEFT JOIN {modules} m ON m.id = cm.module
  192. {$elementjoin}
  193. WHERE ctx.id {$insql}
  194. ORDER BY ctx.path DESC";
  195. $contextinstances = $DB->get_records_sql($sql, $inparams);
  196. // Check whether this context is a user context, or a child of a user context.
  197. // All children of a User context share the same context and cannot be set individually.
  198. foreach ($contextinstances as $record) {
  199. \context_helper::preload_from_record($record);
  200. $parent = \context::instance_by_id($record->id, false);
  201. if ($parent->contextlevel == CONTEXT_USER) {
  202. // Use the context level value for the user.
  203. return self::get_effective_contextlevel_value(CONTEXT_USER, $element);
  204. }
  205. }
  206. foreach ($contextinstances as $record) {
  207. $parent = \context::instance_by_id($record->id, false);
  208. $checkcontextlevel = false;
  209. if (empty($record->eleid)) {
  210. $checkcontextlevel = true;
  211. }
  212. if (!empty($forcedvalue) && context_instance::NOTSET == $forcedvalue) {
  213. $checkcontextlevel = true;
  214. }
  215. if ($checkcontextlevel) {
  216. // Check for a value at the contextlevel
  217. $forplugin = empty($record->modname) ? '' : $record->modname;
  218. list($purposeid, $categoryid) = self::get_effective_default_contextlevel_purpose_and_category(
  219. $parent->contextlevel, false, false, $forplugin);
  220. $instancevalue = $$fieldname;
  221. if (context_instance::NOTSET != $instancevalue && context_instance::INHERIT != $instancevalue) {
  222. // There is an actual value. Return it.
  223. return self::get_element_instance($element, $instancevalue);
  224. }
  225. } else {
  226. $elementclass = "\\tool_dataprivacy\\{$element}";
  227. $instance = new $elementclass(null, $elementclass::extract_record($record, 'ele'));
  228. $instance->validate();
  229. return $instance;
  230. }
  231. }
  232. throw new coding_exception('Something went wrong, system defaults should be set and we should already have a value.');
  233. }
  234. /**
  235. * Returns the effective value for a context level.
  236. *
  237. * Note that this is different from the effective default context level
  238. * (see get_effective_default_contextlevel_purpose_and_category) as this is returning
  239. * the value set in the data registry, not in the defaults page.
  240. *
  241. * @param int $contextlevel
  242. * @param string $element 'category' or 'purpose'
  243. * @return \tool_dataprivacy\purpose|false
  244. */
  245. public static function get_effective_contextlevel_value($contextlevel, $element) {
  246. if ($element !== 'purpose' && $element !== 'category') {
  247. throw new coding_exception('Only \'purpose\' and \'category\' are supported.');
  248. }
  249. $fieldname = $element . 'id';
  250. if ($contextlevel != CONTEXT_SYSTEM && $contextlevel != CONTEXT_USER) {
  251. throw new \coding_exception('Only context_system and context_user values can be retrieved, no other context levels ' .
  252. 'have a purpose or a category.');
  253. }
  254. list($purposeid, $categoryid) = self::get_effective_default_contextlevel_purpose_and_category($contextlevel);
  255. // Note: The $$fieldname points to either $purposeid, or $categoryid.
  256. if (context_instance::NOTSET != $$fieldname && context_instance::INHERIT != $$fieldname) {
  257. // There is a specific value set.
  258. return self::get_element_instance($element, $$fieldname);
  259. }
  260. throw new coding_exception('Something went wrong, system defaults should be set and we should already have a value.');
  261. }
  262. /**
  263. * Returns the effective default purpose and category for a context level.
  264. *
  265. * @param int $contextlevel
  266. * @param int|bool $forcedpurposevalue Use this value as if this was this context level purpose.
  267. * @param int|bool $forcedcategoryvalue Use this value as if this was this context level category.
  268. * @param string $component The name of the component to check.
  269. * @return int[]
  270. */
  271. public static function get_effective_default_contextlevel_purpose_and_category($contextlevel, $forcedpurposevalue = false,
  272. $forcedcategoryvalue = false, $component = '') {
  273. // Get the defaults for this context level.
  274. list($purposeid, $categoryid) = self::get_defaults($contextlevel, $component);
  275. // Honour forced values.
  276. if ($forcedpurposevalue) {
  277. $purposeid = $forcedpurposevalue;
  278. }
  279. if ($forcedcategoryvalue) {
  280. $categoryid = $forcedcategoryvalue;
  281. }
  282. if ($contextlevel == CONTEXT_USER) {
  283. // Only user context levels inherit from a parent context level.
  284. list($parentpurposeid, $parentcategoryid) = self::get_defaults(CONTEXT_SYSTEM);
  285. if (context_instance::INHERIT == $purposeid || context_instance::NOTSET == $purposeid) {
  286. $purposeid = (int)$parentpurposeid;
  287. }
  288. if (context_instance::INHERIT == $categoryid || context_instance::NOTSET == $categoryid) {
  289. $categoryid = $parentcategoryid;
  290. }
  291. }
  292. return [$purposeid, $categoryid];
  293. }
  294. /**
  295. * Returns an instance of the provided element.
  296. *
  297. * @throws \coding_exception
  298. * @param string $element The element name 'purpose' or 'category'
  299. * @param int $id The element id
  300. * @return \core\persistent
  301. */
  302. private static function get_element_instance($element, $id) {
  303. if ($element !== 'purpose' && $element !== 'category') {
  304. throw new coding_exception('No other elements than purpose and category are allowed');
  305. }
  306. $classname = '\tool_dataprivacy\\' . $element;
  307. return new $classname($id);
  308. }
  309. }