PageRenderTime 43ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/export/lib.php

https://github.com/ybozhko/phd-mahara
PHP | 398 lines | 216 code | 29 blank | 153 comment | 16 complexity | 0b52f219c3cf93c8f7c2b3b812430839 MD5 | raw file
Possible License(s): GPL-3.0, MIT, AGPL-1.0, LGPL-2.1
  1. <?php
  2. /**
  3. * Mahara: Electronic portfolio, weblog, resume builder and social networking
  4. * Copyright (C) 2006-2009 Catalyst IT Ltd and others; see:
  5. * http://wiki.mahara.org/Contributors
  6. *
  7. * This program is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU General Public License as published by
  9. * the Free Software Foundation, either version 3 of the License, or
  10. * (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. *
  20. * @package mahara
  21. * @subpackage export
  22. * @author Catalyst IT Ltd
  23. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL
  24. * @copyright (C) 2006-2009 Catalyst IT Ltd http://catalyst.net.nz
  25. *
  26. */
  27. defined('INTERNAL') || die();
  28. require_once('view.php');
  29. require_once(get_config('docroot') . '/artefact/lib.php');
  30. /**
  31. * Base class for all Export plugins.
  32. *
  33. * This class does some basic setup for export plugins, as well as interfacing
  34. * with the Mahara Plugin API. Mostly, the work of generating exports is
  35. * delegated to the plugins themselves.
  36. *
  37. * TODO: split generation of an archive file from the export() method,
  38. * implement zipping the export in a method in this class to reduce
  39. * duplication.
  40. */
  41. abstract class PluginExport extends Plugin {
  42. /**
  43. * Export all views owned by this user
  44. */
  45. const EXPORT_ALL_VIEWS = -1;
  46. /**
  47. * Export only certain views - used internally when a list of views is
  48. * passed to the constructor
  49. */
  50. const EXPORT_LIST_OF_VIEWS = -2;
  51. /**
  52. * Export all artefacts owned by this user
  53. */
  54. const EXPORT_ALL_ARTEFACTS = -3;
  55. /**
  56. * Export artefacts that are part of the views to be exported
  57. */
  58. const EXPORT_ARTEFACTS_FOR_VIEWS = -4;
  59. /**
  60. * Export only certain artefacts - used internally when a list of artefacts
  61. * is passed to the constructor
  62. */
  63. const EXPORT_LIST_OF_ARTEFACTS = -5;
  64. /**
  65. * A human-readable title for the export
  66. */
  67. abstract public static function get_title();
  68. /**
  69. * A human-readable description for the export
  70. */
  71. abstract public static function get_description();
  72. /**
  73. * Perform the export and return the path to the resulting file.
  74. *
  75. * @return string path to the resulting file (relative to dataroot)
  76. */
  77. abstract public function export();
  78. // MAIN CLASS DEFINITION
  79. /**
  80. * List of artefacts to export. Set up by constructor.
  81. */
  82. public $artefacts = array();
  83. /**
  84. * List of views to export. Set up by constructor.
  85. */
  86. public $views = array();
  87. /**
  88. * User object for the user being exported.
  89. */
  90. protected $user;
  91. /**
  92. * Represents the mode for exporting views - one of the class consts
  93. * defined above
  94. */
  95. protected $viewexportmode;
  96. /**
  97. * Represents the mode for exporting artefacts - one of the class consts
  98. * defined above
  99. */
  100. protected $artefactexportmode;
  101. /**
  102. * The time the export was generated.
  103. *
  104. * Technically, this is the time at which the export object was created,
  105. * not the time at which export() was called.
  106. */
  107. protected $exporttime;
  108. /**
  109. * Callback to notify when progress is made
  110. */
  111. private $progresscallback = null;
  112. /**
  113. * Establishes exactly what views and artefacts are to be exported, and
  114. * sets up temporary export directories
  115. *
  116. * Subclasses can override this if they need to do anything else, but
  117. * they must call parent::__construct.
  118. *
  119. * @param User $user The user to export data for
  120. * @param mixed $views can be:
  121. * - PluginExport::EXPORT_ALL_VIEWS
  122. * - array, containing:
  123. * - int - view ids
  124. * - stdclass objects - db rows
  125. * - View objects
  126. * @param mixed $artefacts can be:
  127. * - PluginExport::EXPORT_ALL_ARTEFACTS
  128. * - PluginExport::EXPORT_ARTEFACTS_FOR_VIEWS
  129. * - array, containing:
  130. * - int - artefact ids
  131. * - stdclass objects - db rows
  132. * - ArtefactType subclasses
  133. */
  134. public function __construct(User $user, $views, $artefacts, $progresscallback=null) {
  135. if (!is_null($progresscallback)) {
  136. if (is_callable($progresscallback)) {
  137. $this->progresscallback = $progresscallback;
  138. }
  139. else {
  140. throw new SystemException("The specified progress callback isn't callable");
  141. }
  142. }
  143. $this->notify_progress_callback(0, 'Starting');
  144. $this->exporttime = time();
  145. $this->user = $user;
  146. $userid = $this->user->get('id');
  147. $tmpviews = array();
  148. $tmpartefacts = array();
  149. // Get the list of views to export
  150. if ($views == self::EXPORT_ALL_VIEWS) {
  151. $tmpviews = get_column_sql('SELECT id FROM {view} WHERE owner = ? ORDER BY id', array($userid));
  152. $this->viewexportmode = $views;
  153. }
  154. else if (is_array($views)) {
  155. $tmpviews = $views;
  156. $this->viewexportmode = self::EXPORT_LIST_OF_VIEWS;
  157. }
  158. foreach ($tmpviews as $v) {
  159. $view = null;
  160. if ($v instanceof View) {
  161. $view = $v;
  162. }
  163. else if (is_object($v)) {
  164. $view = new View($v->id, $v);
  165. }
  166. else if (is_numeric($v)) {
  167. $view = new View($v);
  168. }
  169. if (is_null($view)) {
  170. throw new ParamOutOfRangeException("Invalid view $v");
  171. }
  172. if ($view->get('owner') != $userid) {
  173. throw new UserException("User $userid does not own view " . $view->get('id'));
  174. }
  175. $this->views[$view->get('id')] = $view;
  176. }
  177. // Get the list of artefacts to export
  178. if ($artefacts == self::EXPORT_ALL_ARTEFACTS) {
  179. $tmpartefacts = get_column_sql('SELECT id
  180. FROM {artefact}
  181. WHERE "owner" = ?
  182. UNION
  183. SELECT artefact
  184. FROM {view_artefact}
  185. WHERE "view" IN (SELECT id FROM {view} WHERE "owner" = ?)
  186. ORDER BY id', array($userid, $userid));
  187. $this->artefactexportmode = $artefacts;
  188. }
  189. else {
  190. if ($tmpviews) {
  191. $sql = "SELECT DISTINCT va.artefact
  192. FROM {view_artefact} va
  193. LEFT JOIN {view} v ON v.id = va.view
  194. WHERE v.owner = ?
  195. AND va.view IN ( " . implode(',', array_keys($this->views)) . ")
  196. ORDER BY va.artefact";
  197. $tmpartefacts = (array)get_column_sql($sql, array($userid));
  198. // Some artefacts are not inside the view, but still need to be exported with it
  199. $tmpartefacts = array_unique(array_merge($tmpartefacts, $this->get_view_extra_artefacts()));
  200. $tmpartefacts = artefact_get_descendants($tmpartefacts);
  201. $tmpartefacts = array_unique(array_merge($tmpartefacts, $this->get_artefact_extra_artefacts($tmpartefacts)));
  202. }
  203. if ($artefacts == self::EXPORT_ARTEFACTS_FOR_VIEWS) {
  204. $this->artefactexportmode = $artefacts;
  205. }
  206. else {
  207. $tmpartefacts = array_unique(array_merge($tmpartefacts, $artefacts));
  208. $this->artefactexportmode = self::EXPORT_LIST_OF_ARTEFACTS;
  209. }
  210. }
  211. $typestoplugins = get_records_assoc('artefact_installed_type');
  212. foreach ($tmpartefacts as $a) {
  213. $artefact = null;
  214. if ($a instanceof ArefactType) {
  215. $artefact = $a;
  216. }
  217. else if (is_object($a) && isset($a->id)) {
  218. $artefact = artefact_instance_from_id($a->id);
  219. }
  220. else if (is_numeric($a)) {
  221. $artefact = artefact_instance_from_id($a);
  222. }
  223. if (is_null($artefact)) {
  224. throw new ParamOutOfRangeException("Invalid artefact $a");
  225. }
  226. // This check won't work, at the _least_ because at the time of
  227. // writing, can_view_artefact does not support normal users viewing
  228. // site files. This check is also pretty damn slow. So think twice
  229. // before uncommenting it. I presume if you _are_ uncommenting it,
  230. // it's because you're trying to isloate a security vulnerability
  231. // where a user can export another user's files or something. In
  232. // which case you'll be being careful anyway, I hope.
  233. //if (!$this->user->can_view_artefact($artefact)) {
  234. // throw new SystemException("User $userid does not own artefact " . $artefact->get('id'));
  235. //}
  236. if ($artefact->exportable()) {
  237. $this->artefacts[$artefact->get('id')] = $artefact;
  238. }
  239. }
  240. $this->collections = array();
  241. $collections = get_records_sql_assoc('
  242. SELECT * FROM {collection} WHERE id IN (
  243. SELECT collection
  244. FROM {collection_view}
  245. WHERE view IN (' . join(',', array_keys($this->views)) . ')
  246. )',
  247. array()
  248. );
  249. if ($collections) {
  250. require_once('collection.php');
  251. foreach ($collections as &$c) {
  252. $this->collections[$c->id] = new Collection(0, $c);
  253. }
  254. }
  255. // Now set up the temporary export directories
  256. $this->exportdir = get_config('dataroot')
  257. . 'export/'
  258. . $this->user->get('id') . '/'
  259. . $this->exporttime . '/';
  260. if (!check_dir_exists($this->exportdir)) {
  261. throw new SystemException("Couldn't create the temporary export directory $this->exportdir");
  262. }
  263. $this->notify_progress_callback(10, 'Setup');
  264. }
  265. /**
  266. * Accessor
  267. *
  268. * @param string $field The field to get (see the class definition to find
  269. * which fields are available)
  270. */
  271. public function get($field) {
  272. if (!property_exists($this, $field)) {
  273. throw new ParamOutOfRangeException("Field $field wasn't found in class " . get_class($this));
  274. }
  275. return $this->{$field};
  276. }
  277. /**
  278. * Notifies the registered progress callback about the progress in generating the export.
  279. *
  280. * This is provided as exports can take a long time to generate. Export
  281. * plugins are encouraged to call this at least after performing some major
  282. * operation, and should always call it saying when the execution of
  283. * export() is done. However, it is unnecessary to call it too often.
  284. *
  285. * For testing purposes, you may find it useful to register a progress
  286. * callback that simply log_debug()s the data, so you can check that the
  287. * percentage is always increasing, for example.
  288. *
  289. * @param int $percent The total percentage of the way through generating
  290. * the export. The base class constructor hands over
  291. * control claiming 10% of the work is done.
  292. * @param string $status A string describing the current status of the
  293. * export - e.g. 'Exporting Artefact (20/75)'
  294. */
  295. protected function notify_progress_callback($percent, $status) {
  296. if ($this->progresscallback) {
  297. call_user_func_array($this->progresscallback, array(
  298. $percent, $status
  299. ));
  300. }
  301. }
  302. /**
  303. * Artefact plugins can specify additional artefacts required for view export
  304. */
  305. protected function get_view_extra_artefacts() {
  306. $extra = array();
  307. $plugins = plugins_installed('artefact');
  308. foreach ($plugins as &$plugin) {
  309. safe_require('artefact', $plugin->name);
  310. $classname = generate_class_name('artefact', $plugin->name);
  311. if (is_callable($classname . '::view_export_extra_artefacts')) {
  312. if ($artefacts = call_static_method($classname, 'view_export_extra_artefacts', array_keys($this->views))) {
  313. $extra = array_unique(array_merge($extra, $artefacts));
  314. }
  315. }
  316. }
  317. return $extra;
  318. }
  319. protected function get_artefact_extra_artefacts(&$artefactids) {
  320. if (empty($artefactids)) {
  321. return array();
  322. }
  323. $extra = array();
  324. $plugins = plugins_installed('artefact');
  325. foreach ($plugins as &$plugin) {
  326. safe_require('artefact', $plugin->name);
  327. $classname = generate_class_name('artefact', $plugin->name);
  328. if (is_callable($classname . '::artefact_export_extra_artefacts')) {
  329. if ($artefacts = call_static_method($classname, 'artefact_export_extra_artefacts', $artefactids)) {
  330. $extra = array_unique(array_merge($extra, $artefacts));
  331. }
  332. }
  333. }
  334. return $extra;
  335. }
  336. }
  337. /**
  338. * Looks in the export staging area in dataroot and deletes old, unneeded
  339. * exports.
  340. */
  341. function export_cleanup_old_exports() {
  342. require_once('file.php');
  343. $basedir = get_config('dataroot') . 'export/';
  344. $exportdir = new DirectoryIterator($basedir);
  345. $mintime = time() - (12 * 60 * 60); // delete exports older than 12 hours
  346. // The export dir contains one directory for each user who has created
  347. // an export, named after their UID
  348. foreach ($exportdir as $userdir) {
  349. if ($userdir->isDot()) continue;
  350. // Each user's directory contains one directory for each export
  351. // they made, named as the unix timestamp of the time they
  352. // generated it
  353. $udir = new DirectoryIterator($basedir . $userdir->getFilename());
  354. foreach ($udir as $dir) {
  355. if ($dir->isDot()) continue;
  356. if ($dir->getCTime() < $mintime) {
  357. rmdirr($basedir . $userdir->getFilename() . '/' . $dir->getFilename());
  358. }
  359. }
  360. }
  361. }
  362. ?>