ExportOds.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. <?php
  2. /**
  3. * Set of functions used to build OpenDocument Spreadsheet dumps of tables
  4. */
  5. declare(strict_types=1);
  6. namespace PhpMyAdmin\Plugins\Export;
  7. use PhpMyAdmin\DatabaseInterface;
  8. use PhpMyAdmin\OpenDocument;
  9. use PhpMyAdmin\Plugins\ExportPlugin;
  10. use PhpMyAdmin\Properties\Options\Groups\OptionsPropertyMainGroup;
  11. use PhpMyAdmin\Properties\Options\Groups\OptionsPropertyRootGroup;
  12. use PhpMyAdmin\Properties\Options\Items\BoolPropertyItem;
  13. use PhpMyAdmin\Properties\Options\Items\HiddenPropertyItem;
  14. use PhpMyAdmin\Properties\Options\Items\TextPropertyItem;
  15. use PhpMyAdmin\Properties\Plugins\ExportPluginProperties;
  16. use function bin2hex;
  17. use function date;
  18. use function htmlspecialchars;
  19. use function stripos;
  20. use function stripslashes;
  21. use function strtotime;
  22. /**
  23. * Handles the export for the ODS class
  24. */
  25. class ExportOds extends ExportPlugin
  26. {
  27. public function __construct()
  28. {
  29. parent::__construct();
  30. $GLOBALS['ods_buffer'] = '';
  31. $this->setProperties();
  32. }
  33. /**
  34. * Sets the export ODS properties
  35. *
  36. * @return void
  37. */
  38. protected function setProperties()
  39. {
  40. $exportPluginProperties = new ExportPluginProperties();
  41. $exportPluginProperties->setText('OpenDocument Spreadsheet');
  42. $exportPluginProperties->setExtension('ods');
  43. $exportPluginProperties->setMimeType(
  44. 'application/vnd.oasis.opendocument.spreadsheet'
  45. );
  46. $exportPluginProperties->setForceFile(true);
  47. $exportPluginProperties->setOptionsText(__('Options'));
  48. // create the root group that will be the options field for
  49. // $exportPluginProperties
  50. // this will be shown as "Format specific options"
  51. $exportSpecificOptions = new OptionsPropertyRootGroup(
  52. 'Format Specific Options'
  53. );
  54. // general options main group
  55. $generalOptions = new OptionsPropertyMainGroup('general_opts');
  56. // create primary items and add them to the group
  57. $leaf = new TextPropertyItem(
  58. 'null',
  59. __('Replace NULL with:')
  60. );
  61. $generalOptions->addProperty($leaf);
  62. $leaf = new BoolPropertyItem(
  63. 'columns',
  64. __('Put columns names in the first row')
  65. );
  66. $generalOptions->addProperty($leaf);
  67. $leaf = new HiddenPropertyItem('structure_or_data');
  68. $generalOptions->addProperty($leaf);
  69. // add the main group to the root group
  70. $exportSpecificOptions->addProperty($generalOptions);
  71. // set the options for the export plugin property item
  72. $exportPluginProperties->setOptions($exportSpecificOptions);
  73. $this->properties = $exportPluginProperties;
  74. }
  75. /**
  76. * Outputs export header
  77. *
  78. * @return bool Whether it succeeded
  79. */
  80. public function exportHeader()
  81. {
  82. $GLOBALS['ods_buffer'] .= '<?xml version="1.0" encoding="utf-8"?' . '>'
  83. . '<office:document-content '
  84. . OpenDocument::NS . ' office:version="1.0">'
  85. . '<office:automatic-styles>'
  86. . '<number:date-style style:name="N37"'
  87. . ' number:automatic-order="true">'
  88. . '<number:month number:style="long"/>'
  89. . '<number:text>/</number:text>'
  90. . '<number:day number:style="long"/>'
  91. . '<number:text>/</number:text>'
  92. . '<number:year/>'
  93. . '</number:date-style>'
  94. . '<number:time-style style:name="N43">'
  95. . '<number:hours number:style="long"/>'
  96. . '<number:text>:</number:text>'
  97. . '<number:minutes number:style="long"/>'
  98. . '<number:text>:</number:text>'
  99. . '<number:seconds number:style="long"/>'
  100. . '<number:text> </number:text>'
  101. . '<number:am-pm/>'
  102. . '</number:time-style>'
  103. . '<number:date-style style:name="N50"'
  104. . ' number:automatic-order="true"'
  105. . ' number:format-source="language">'
  106. . '<number:month/>'
  107. . '<number:text>/</number:text>'
  108. . '<number:day/>'
  109. . '<number:text>/</number:text>'
  110. . '<number:year/>'
  111. . '<number:text> </number:text>'
  112. . '<number:hours number:style="long"/>'
  113. . '<number:text>:</number:text>'
  114. . '<number:minutes number:style="long"/>'
  115. . '<number:text> </number:text>'
  116. . '<number:am-pm/>'
  117. . '</number:date-style>'
  118. . '<style:style style:name="DateCell" style:family="table-cell"'
  119. . ' style:parent-style-name="Default" style:data-style-name="N37"/>'
  120. . '<style:style style:name="TimeCell" style:family="table-cell"'
  121. . ' style:parent-style-name="Default" style:data-style-name="N43"/>'
  122. . '<style:style style:name="DateTimeCell" style:family="table-cell"'
  123. . ' style:parent-style-name="Default" style:data-style-name="N50"/>'
  124. . '</office:automatic-styles>'
  125. . '<office:body>'
  126. . '<office:spreadsheet>';
  127. return true;
  128. }
  129. /**
  130. * Outputs export footer
  131. *
  132. * @return bool Whether it succeeded
  133. */
  134. public function exportFooter()
  135. {
  136. $GLOBALS['ods_buffer'] .= '</office:spreadsheet>'
  137. . '</office:body>'
  138. . '</office:document-content>';
  139. return $this->export->outputHandler(
  140. OpenDocument::create(
  141. 'application/vnd.oasis.opendocument.spreadsheet',
  142. $GLOBALS['ods_buffer']
  143. )
  144. );
  145. }
  146. /**
  147. * Outputs database header
  148. *
  149. * @param string $db Database name
  150. * @param string $db_alias Aliases of db
  151. *
  152. * @return bool Whether it succeeded
  153. */
  154. public function exportDBHeader($db, $db_alias = '')
  155. {
  156. return true;
  157. }
  158. /**
  159. * Outputs database footer
  160. *
  161. * @param string $db Database name
  162. *
  163. * @return bool Whether it succeeded
  164. */
  165. public function exportDBFooter($db)
  166. {
  167. return true;
  168. }
  169. /**
  170. * Outputs CREATE DATABASE statement
  171. *
  172. * @param string $db Database name
  173. * @param string $export_type 'server', 'database', 'table'
  174. * @param string $db_alias Aliases of db
  175. *
  176. * @return bool Whether it succeeded
  177. */
  178. public function exportDBCreate($db, $export_type, $db_alias = '')
  179. {
  180. return true;
  181. }
  182. /**
  183. * Outputs the content of a table in NHibernate format
  184. *
  185. * @param string $db database name
  186. * @param string $table table name
  187. * @param string $crlf the end of line sequence
  188. * @param string $error_url the url to go back in case of error
  189. * @param string $sql_query SQL query for obtaining data
  190. * @param array $aliases Aliases of db/table/columns
  191. *
  192. * @return bool Whether it succeeded
  193. */
  194. public function exportData(
  195. $db,
  196. $table,
  197. $crlf,
  198. $error_url,
  199. $sql_query,
  200. array $aliases = []
  201. ) {
  202. global $what, $dbi;
  203. $db_alias = $db;
  204. $table_alias = $table;
  205. $this->initAlias($aliases, $db_alias, $table_alias);
  206. // Gets the data from the database
  207. $result = $dbi->query(
  208. $sql_query,
  209. DatabaseInterface::CONNECT_USER,
  210. DatabaseInterface::QUERY_UNBUFFERED
  211. );
  212. $fields_cnt = $dbi->numFields($result);
  213. $fields_meta = $dbi->getFieldsMeta($result);
  214. $field_flags = [];
  215. for ($j = 0; $j < $fields_cnt; $j++) {
  216. $field_flags[$j] = $dbi->fieldFlags($result, $j);
  217. }
  218. $GLOBALS['ods_buffer']
  219. .= '<table:table table:name="' . htmlspecialchars($table_alias) . '">';
  220. // If required, get fields name at the first line
  221. if (isset($GLOBALS[$what . '_columns'])) {
  222. $GLOBALS['ods_buffer'] .= '<table:table-row>';
  223. for ($i = 0; $i < $fields_cnt; $i++) {
  224. $col_as = $dbi->fieldName($result, $i);
  225. if (! empty($aliases[$db]['tables'][$table]['columns'][$col_as])) {
  226. $col_as = $aliases[$db]['tables'][$table]['columns'][$col_as];
  227. }
  228. $GLOBALS['ods_buffer']
  229. .= '<table:table-cell office:value-type="string">'
  230. . '<text:p>'
  231. . htmlspecialchars(
  232. stripslashes($col_as)
  233. )
  234. . '</text:p>'
  235. . '</table:table-cell>';
  236. }
  237. $GLOBALS['ods_buffer'] .= '</table:table-row>';
  238. }
  239. // Format the data
  240. while ($row = $dbi->fetchRow($result)) {
  241. $GLOBALS['ods_buffer'] .= '<table:table-row>';
  242. for ($j = 0; $j < $fields_cnt; $j++) {
  243. if ($fields_meta[$j]->type === 'geometry') {
  244. // export GIS types as hex
  245. $row[$j] = '0x' . bin2hex($row[$j]);
  246. }
  247. if (! isset($row[$j]) || $row[$j] === null) {
  248. $GLOBALS['ods_buffer']
  249. .= '<table:table-cell office:value-type="string">'
  250. . '<text:p>'
  251. . htmlspecialchars($GLOBALS[$what . '_null'])
  252. . '</text:p>'
  253. . '</table:table-cell>';
  254. } elseif (stripos($field_flags[$j], 'BINARY') !== false
  255. && $fields_meta[$j]->blob
  256. ) {
  257. // ignore BLOB
  258. $GLOBALS['ods_buffer']
  259. .= '<table:table-cell office:value-type="string">'
  260. . '<text:p></text:p>'
  261. . '</table:table-cell>';
  262. } elseif ($fields_meta[$j]->type === 'date') {
  263. $GLOBALS['ods_buffer']
  264. .= '<table:table-cell office:value-type="date"'
  265. . ' office:date-value="'
  266. . date('Y-m-d', strtotime($row[$j]))
  267. . '" table:style-name="DateCell">'
  268. . '<text:p>'
  269. . htmlspecialchars($row[$j])
  270. . '</text:p>'
  271. . '</table:table-cell>';
  272. } elseif ($fields_meta[$j]->type === 'time') {
  273. $GLOBALS['ods_buffer']
  274. .= '<table:table-cell office:value-type="time"'
  275. . ' office:time-value="'
  276. . date('\P\TH\Hi\Ms\S', strtotime($row[$j]))
  277. . '" table:style-name="TimeCell">'
  278. . '<text:p>'
  279. . htmlspecialchars($row[$j])
  280. . '</text:p>'
  281. . '</table:table-cell>';
  282. } elseif ($fields_meta[$j]->type === 'datetime') {
  283. $GLOBALS['ods_buffer']
  284. .= '<table:table-cell office:value-type="date"'
  285. . ' office:date-value="'
  286. . date('Y-m-d\TH:i:s', strtotime($row[$j]))
  287. . '" table:style-name="DateTimeCell">'
  288. . '<text:p>'
  289. . htmlspecialchars($row[$j])
  290. . '</text:p>'
  291. . '</table:table-cell>';
  292. } elseif (($fields_meta[$j]->numeric
  293. && $fields_meta[$j]->type !== 'timestamp'
  294. && ! $fields_meta[$j]->blob)
  295. || $fields_meta[$j]->type === 'real'
  296. ) {
  297. $GLOBALS['ods_buffer']
  298. .= '<table:table-cell office:value-type="float"'
  299. . ' office:value="' . $row[$j] . '" >'
  300. . '<text:p>'
  301. . htmlspecialchars($row[$j])
  302. . '</text:p>'
  303. . '</table:table-cell>';
  304. } else {
  305. $GLOBALS['ods_buffer']
  306. .= '<table:table-cell office:value-type="string">'
  307. . '<text:p>'
  308. . htmlspecialchars($row[$j])
  309. . '</text:p>'
  310. . '</table:table-cell>';
  311. }
  312. }
  313. $GLOBALS['ods_buffer'] .= '</table:table-row>';
  314. }
  315. $dbi->freeResult($result);
  316. $GLOBALS['ods_buffer'] .= '</table:table>';
  317. return true;
  318. }
  319. /**
  320. * Outputs result raw query in ODS format
  321. *
  322. * @param string $err_url the url to go back in case of error
  323. * @param string $sql_query the rawquery to output
  324. * @param string $crlf the end of line sequence
  325. *
  326. * @return bool if succeeded
  327. */
  328. public function exportRawQuery(string $err_url, string $sql_query, string $crlf): bool
  329. {
  330. return $this->exportData('', '', $crlf, $err_url, $sql_query);
  331. }
  332. }