InlineGoogleSpreadsheetViewerPlugin

The Inline Google Spreadsheet Viewer InlineGoogleSpreadsheetViewerPlugin class.

Defined (1)

The class is defined in the following location(s).

/inline-gdocs-viewer.php  
  1. class InlineGoogleSpreadsheetViewerPlugin { 
  2.  
  3. private $shortcode = 'gdoc'; 
  4. private $dt_class = 'igsv-table'; //< Default table class. 
  5. private $dt_defaults; //< Defaults for DataTables defaults object. 
  6. private $invocations = 0; 
  7. private $prefix; //< Internal prefix for settings, etc., derived from shortcode. 
  8. private $capabilities; //< List of custom capabilities. 
  9. private $gdoc_url_regex = 
  10. '!https://(?:docs\.google\.com/spreadsheets/d/|script\.google\.com/macros/s/)([^/]+)!'; 
  11.  
  12. public function __construct () { 
  13. // Initialize private defaults. 
  14. $this->prefix = $this->shortcode . '_'; 
  15. $this->dt_defaults = json_encode(array( 
  16. 'dom' => "TC<'clear'>lfrtip" 
  17. )); 
  18. $this->capabilities = array( 
  19. $this->prefix . 'query_sql_databases' 
  20. ); 
  21.  
  22. add_action('plugins_loaded', array($this, 'registerL10n')); 
  23. add_action('init', array($this, 'maybeFetchGvizDataSource')); 
  24. add_action('admin_init', array($this, 'registerSettings')); 
  25. add_action('admin_menu', array($this, 'registerAdminMenu')); 
  26. add_action('admin_head', array($this, 'doAdminHeadActions')); 
  27. add_action('admin_enqueue_scripts', array($this, 'addAdminScripts')); 
  28. add_action('admin_print_footer_scripts', array($this, 'addQuickTagButton')); 
  29.  
  30. add_action('wp_enqueue_scripts', array($this, 'addFrontEndScripts')); 
  31.  
  32. add_shortcode($this->shortcode, array($this, 'displayShortcode')); 
  33. wp_embed_register_handler( 
  34. $this->shortcode . 'spreadsheet',  
  35. $this->gdoc_url_regex,  
  36. array($this, 'oEmbedHandler') 
  37. ); 
  38.  
  39. register_activation_hook(__FILE__, array($this, 'activate')); 
  40.  
  41. public function activate () { 
  42. $options = get_option($this->prefix . 'settings'); 
  43. if (!isset($options['datatables_classes'])) { $options['datatables_classes'] = $this->dt_class; } 
  44. if (empty($options['datatables_defaults_object'])) { 
  45. $options['datatables_defaults_object'] = json_decode($this->dt_defaults); 
  46. update_option($this->prefix . 'settings', $options); 
  47. $admin_role = get_role('administrator'); 
  48. $admin_role->add_cap($this->prefix . 'query_sql_databases', true); 
  49.  
  50. public function registerL10n () { 
  51. load_plugin_textdomain('inline-gdocs-viewer', false, dirname(plugin_basename(__FILE__)) . '/languages/'); 
  52.  
  53. public function registerSettings () { 
  54. register_setting( 
  55. $this->prefix . 'settings',  
  56. $this->prefix . 'settings',  
  57. array($this, 'validateSettings') 
  58. ); 
  59.  
  60. public function registerAdminMenu () { 
  61. add_options_page( 
  62. __('Inline Google Spreadsheet Viewer Settings', 'inline-gdocs-viewer'),  
  63. __('Inline Google Spreadsheet Viewer', 'inline-gdocs-viewer'),  
  64. 'manage_options',  
  65. $this->prefix . 'settings',  
  66. array($this, 'renderOptionsPage') 
  67. ); 
  68.  
  69. public function doAdminHeadActions () { 
  70. $this->registerContextualHelp(); 
  71.  
  72. public function addAdminScripts () { 
  73. wp_enqueue_style('wp-jquery-ui-dialog'); 
  74. wp_enqueue_script('jquery-ui-dialog'); 
  75. wp_enqueue_script('jquery-ui-tabs'); 
  76. //wp_enqueue_script('jquery-ui-tooltip'); 
  77.  
  78. wp_enqueue_style( 
  79. 'inline-gdocs-viewer',  
  80. plugins_url('inline-gdocs-viewer.css', __FILE__) 
  81. ); 
  82.  
  83. public function addFrontEndScripts () { 
  84. // Core DataTables. 
  85. wp_enqueue_style( 
  86. 'jquery-datatables',  
  87. '//cdn.datatables.net/1.10.6/css/jquery.dataTables.min.css' 
  88. ); 
  89. wp_enqueue_script( 
  90. 'jquery-datatables',  
  91. '//cdn.datatables.net/1.10.6/js/jquery.dataTables.min.js',  
  92. array('jquery') 
  93. ); 
  94. // DataTables extensions. 
  95. wp_enqueue_style( 
  96. 'datatables-colvis',  
  97. '//cdn.datatables.net/colvis/1.1.0/css/dataTables.colVis.css' 
  98. ); 
  99. wp_enqueue_script( 
  100. 'datatables-colvis',  
  101. '//cdn.datatables.net/colvis/1.1.0/js/dataTables.colVis.min.js',  
  102. array('jquery-datatables') 
  103. ); 
  104. wp_enqueue_style( 
  105. 'datatables-tabletools',  
  106. '//cdn.datatables.net/tabletools/2.2.1/css/dataTables.tableTools.css' 
  107. ); 
  108. wp_enqueue_script( 
  109. 'datatables-tabletools',  
  110. '//cdn.datatables.net/tabletools/2.2.1/js/dataTables.tableTools.min.js',  
  111. array('jquery-datatables') 
  112. ); 
  113. wp_enqueue_style( 
  114. 'datatables-fixedheader',  
  115. '//datatables.net/release-datatables/extensions/FixedHeader/css/dataTables.fixedHeader.css' 
  116. ); 
  117. wp_enqueue_script( 
  118. 'datatables-fixedheader',  
  119. '//datatables.net/release-datatables/extensions/FixedHeader/js/dataTables.fixedHeader.js',  
  120. array('jquery-datatables') 
  121. ); 
  122. wp_enqueue_style( 
  123. 'datatables-fixedcolumns',  
  124. '//datatables.net/release-datatables/extensions/FixedColumns/css/dataTables.fixedColumns.css' 
  125. ); 
  126. wp_enqueue_script( 
  127. 'datatables-fixedcolumns',  
  128. '//datatables.net/release-datatables/extensions/FixedColumns/js/dataTables.fixedColumns.js',  
  129. array('jquery-datatables') 
  130. ); 
  131. wp_enqueue_style( 
  132. 'datatables-responsive',  
  133. '//cdn.datatables.net/responsive/1.0.4/css/dataTables.responsive.css' 
  134. ); 
  135. wp_enqueue_script( 
  136. 'datatables-responsive',  
  137. '//cdn.datatables.net/responsive/1.0.4/js/dataTables.responsive.js',  
  138. array('jquery-datatables') 
  139. ); 
  140. wp_enqueue_script( 
  141. 'igsv-datatables',  
  142. plugins_url('igsv-datatables.js', __FILE__),  
  143. array('jquery-datatables') 
  144. ); 
  145. wp_localize_script('igsv-datatables', 'igsv_plugin_vars', $this->getLocalizedPluginVars()); 
  146.  
  147. // Google Charts and Visualization libraries 
  148. wp_enqueue_script('google-ajax-api', '//www.google.com/jsapi'); 
  149. wp_enqueue_script( 
  150. 'igsv-gvizcharts',  
  151. plugins_url('igsv-gvizcharts.js', __FILE__),  
  152. array('google-ajax-api') 
  153. ); 
  154.  
  155. /** 
  156. * Deterministically makes a unique transient name. 
  157. * @param string $key The ID of the document, extracted from the key attribute of the shortcode. 
  158. * @param string $q The query, if one exists, from the query attribute of the shortcode. 
  159. * @return string A 40 character unique string representing the name of the transient for this key and query. 
  160. * @see https://codex.wordpress.org/Transients_API 
  161. */ 
  162. private function getTransientName ($key, $q, $gid) { 
  163. return substr($this->shortcode . hash('sha1', $this->shortcode . $key . $q . $gid), 0, 40); 
  164.  
  165. /** 
  166. * This simple getter/setter pair works around a bug in WP's own 
  167. * serialization, apparently, by serializing the data ourselves 
  168. * and then base64 encoding it. 
  169. */ 
  170. private function getTransient ($transient) { 
  171. return unserialize(base64_decode(get_transient($transient))); 
  172. private function setTransient ($transient, $data, $expiry) { 
  173. return set_transient($transient, base64_encode(serialize($data)), $expiry); 
  174.  
  175. /** 
  176. * Lazily tests the provided Google Doc "key" (URL or document ID) 
  177. * to determine what type of document it really is. Valid doc 
  178. * types are one of: `spreadsheet`, `gasapp`, `docsviewer`, or `csv`. 
  179. * @param string $key The key passed from the shortcode. 
  180. * @return string A keyword referring to the type of document the key refers to. 
  181. */ 
  182. private function getDocTypeByKey ($key) { 
  183. $type = ''; 
  184. $p = parse_url($key); 
  185. if ('csv' === strtolower(pathinfo($p['path'], PATHINFO_EXTENSION))) { 
  186. $type = 'csv'; 
  187. } else if (!isset($p['scheme']) && 'wordpress' === $p['path']) { 
  188. $type = 'wpdb'; 
  189. } else if ('mysql' === $p['scheme']) { 
  190. $type = 'mysql'; 
  191. } else if (isset($p['host'])) { 
  192. switch ($p['host']) { 
  193. case 'docs.google.com': 
  194. $type = 'spreadsheet'; 
  195. break; 
  196. case 'script.google.com': 
  197. $type = 'gasapp'; 
  198. break; 
  199. default: 
  200. $type = 'docsviewer'; 
  201. break; 
  202. } else { 
  203. // without a host part, assume an old-style Spreadsheet 
  204. $type = 'spreadsheet'; 
  205. return $type; 
  206.  
  207. private function getSpreadsheetUrl ($atts) { 
  208. $url = ''; 
  209. // Assume a full link. 
  210. $m = array(); 
  211. if (preg_match('/\/(edit|view|pubhtml|htmlview).*$/', $atts['key'], $m) && 'http' === substr($atts['key'], 0, 4)) { 
  212. $parts = parse_url($atts['key']); 
  213. if (!empty($parts['fragment'])) { 
  214. $frag = array(); 
  215. parse_str($parts['fragment'], $frag); 
  216. if ($frag['gid']) { $atts['gid'] = $frag['gid']; } 
  217. $atts['key'] = $parts['scheme'] . '://' . $parts['host'] . $parts['path']; 
  218. $action = ($atts['query'] || $atts['chart']) 
  219. ? 'gviz/tq?tqx=out:csv&tq=' . rawurlencode($atts['query']) 
  220. : 'export?format=csv'; 
  221. $url = str_replace($m[1], $action, $atts['key']); 
  222. if ($atts['gid']) { 
  223. $url .= '&gid=' . $atts['gid']; 
  224. } else { 
  225. $url .= "https://spreadsheets.google.com/pub?key={$atts['key']}&output=csv"; 
  226. if ($gid) { 
  227. $url .= "&single=true&gid={$atts['gid']}"; 
  228. return $url; 
  229.  
  230. private function getGVizDataSourceUrl ($key, $query, $format) { 
  231. $format = ($format) ? $format : 'json'; 
  232. $base = trailingslashit(get_site_url()) . '?'; 
  233. $qs = 'url='; 
  234. $qs .= rawurlencode($key) . '&tq=' . rawurlencode($query) . "&tqx=out:$format"; 
  235. return $base . $qs; 
  236.  
  237. private function sanitizeQuery ($query) { 
  238. // Due to shortcode parsing limitations of angle brackets (< and > characters),  
  239. // manually decode only the URL encoded values for those values, which are 
  240. // themselves expected to be entered manually by the user. That is, to supply 
  241. // the shortcode with a less than sign, the user ought enter %3C, but after 
  242. // the initial urlencode($query), this will encode the percent sign, returning 
  243. // instead the value %253C, so we manually replace this in the query ourselves. 
  244. return rawurldecode(str_replace('%253E', '%3E', str_replace('%253C', '%3C', rawurlencode($query)))); 
  245.  
  246. private function getDocId ($key) { 
  247. $m = array(); 
  248. preg_match($this->gdoc_url_regex, $key, $m); 
  249. if (!empty($m[1])) { 
  250. $id = $m[1]; 
  251. } else { 
  252. $id = sanitize_title_with_dashes($key); 
  253. if ('mysql' === $this->getDocTypeByKey($key)) { 
  254. $id = hash('md5', $key); 
  255. return $id; 
  256.  
  257. /** 
  258. * Retrieves data from the transient cache if available, or via HTTP if not. 
  259. * @param string $url The URL to fetch, if not in cache. 
  260. * @param array $x Values from the shortcode attributes. 
  261. */ 
  262. private function fetchData ($url, $x) { 
  263. $transient = $this->getTransientName($x['key'], $x['query'], $x['gid']); 
  264. if (false === $x['use_cache'] || 'no' === strtolower($x['use_cache'])) { 
  265. delete_transient($transient); 
  266. $http_response = $this->doHttpRequest($url, $x['http_opts']); 
  267. } else { 
  268. if (false === ($http_response = $this->getTransient($transient))) { 
  269. $http_response = $this->doHttpRequest($url, $x['http_opts']); 
  270. $this->setTransient($transient, $http_response, (int) $x['expire_in']); 
  271. return $http_response; 
  272.  
  273. /** 
  274. * Performs an HTTP request as instructed by the shortcode's parameters. 
  275. * @param string $url The URL to request. 
  276. * @param string $http_opts A JSON string representing options to pass to the WordPress HTTP API. 
  277. * @return array $resp The HTTP response from the WordPress HTTP API. 
  278. */ 
  279. private function doHttpRequest ($url, $opts) { 
  280. $http_args = array(); 
  281. if ($opts) { 
  282. try { 
  283. foreach (json_decode($opts) as $k => $v) { 
  284. $http_args[$k] = $v; 
  285. } catch (Exception $e) { 
  286. $this->runtimeError(__('Error parsing HTTP options attribute:', 'inline-gdocs-viewer') . $e->getMessage()); 
  287. $resp = (empty($http_args)) ? wp_remote_get($url) : wp_remote_request($url, $http_args); 
  288. if (is_wp_error($resp)) { // bail on error 
  289. $this->runtimeError(__('Error requesting data:', 'inline-gdocs-viewer') . ' ' . $resp->get_error_message()); 
  290. return $resp; 
  291.  
  292. private function runtimeError ($msg) { 
  293. throw new Exception(esc_html("[{$this->shortcode} $msg]")); 
  294.  
  295. public function parseCsv ($csv_str) { 
  296. return $this->str_getcsv($csv_str); // Yo, why is PHP's built-in str_getcsv() frakking things up? 
  297.  
  298. /** 
  299. * Prints an appropriate HTML attribute string for any HTML5 Data 
  300. * attributes that DataTables can use. 
  301. * @param array $atts Values passed from the shortcode. 
  302. * @return A string representing attribute-value pairs in HTML. 
  303. */ 
  304. function dataTablesAttributes ($atts) { 
  305. $str = ''; 
  306. foreach ($atts as $k => $v) { 
  307. if (0 === strpos($k, 'datatables_') && false !== $v) { 
  308. $k = str_replace('datatables', 'data', str_replace('_', '-', $k)); 
  309. // We urldecode() the value here because WordPress shortcodes 
  310. // use square brackets, but so do JavaScript arrays so users 
  311. // are advised to sometimes enter URL-encoded equivalents. 
  312. $str .= esc_attr($k) . '=\'' . esc_attr(urldecode($v)) . '\' '; 
  313. return $str; 
  314.  
  315. /** 
  316. * Converts a two-dimensional array representing rows and cells of data 
  317. * into an HTML representation of that data, according to any additional 
  318. * options passed to it. 
  319. * @param array $r Multidimensional array representing table data. 
  320. * @param array $options Values passed from the shortcode. 
  321. * @param string $caption Passed via shortcode, should be the table caption. 
  322. * @return An HTML string of the complete <table> element. 
  323. * @see displayShortcode 
  324. */ 
  325. public function dataToHtml ($r, $options, $caption = '') { 
  326. if ($options['strip'] > 0) { $r = array_slice($r, $options['strip']); } // discard 
  327.  
  328. // Split into table headers and body. 
  329. $thead = ((int) $options['header_rows']) ? array_splice($r, 0, $options['header_rows']) : array_splice($r, 0, 1); 
  330. $tbody = $r; 
  331.  
  332. $ir = 1; // row number counter 
  333. $ic = 1; // column number counter 
  334.  
  335. $id = (0 === $this->invocations) 
  336. ? 'igsv-' . $this->getDocId($options['key']) 
  337. : "igsv-{$this->invocations}-" . $this->getDocId($options['key']); 
  338. $html = '<table id="' . esc_attr($id) . '"'; 
  339. // Prepend a space character onto the 'class' value, if one exists. 
  340. if (!empty($options['class'])) { $options['class'] = " {$options['class']}"; } 
  341. $html .= ' class="' . $this->dt_class . esc_attr($options['class']) . '"'; 
  342. $html .= ' lang="' . esc_attr($options['lang']) . '"'; 
  343. $html .= ' summary="' . esc_attr($options['summary']) . '"'; 
  344. $html .= ' title="' . esc_attr($options['title']) . '"'; 
  345. $html .= ' style="' . esc_attr($options['style']) . '"'; 
  346. $html .= (array_search('no-datatables', explode(' ', $options['class']))) 
  347. ? '' 
  348. : ' ' . $this->dataTablesAttributes($options); 
  349. $html .= '>'; 
  350.  
  351. if (!empty($caption)) { 
  352. $html .= '<caption>' . esc_html($caption) . '</caption>'; 
  353.  
  354. $html .= "<thead>\n"; 
  355. foreach ($thead as $v) { 
  356. $html .= "<tr class=\"row-$ir " . $this->evenOrOdd($ir) . "\">"; 
  357. $ir++; 
  358. $ic = 1; // reset column counting 
  359. foreach ($v as $th) { 
  360. $th = nl2br(esc_html($th)); 
  361. $html .= "<th class=\"col-$ic " . $this->evenOrOdd($ic) . "\"><div>$th</div></th>"; 
  362. $ic++; 
  363. $html .= "</tr>"; 
  364. $html .= "</thead><tbody>"; 
  365.  
  366. foreach ($tbody as $v) { 
  367. $html .= "<tr class=\"row-$ir " . $this->evenOrOdd($ir) . "\">"; 
  368. $ir++; 
  369. $ic = 1; // reset column counting 
  370. foreach ($v as $td) { 
  371. $td = nl2br(esc_html($td)); 
  372. $html .= "<td class=\"col-$ic " . $this->evenOrOdd($ic) . "\">$td</td>"; 
  373. $ic++; 
  374. $html .= "</tr>"; 
  375. $html .= '</tbody></table>'; 
  376.  
  377. $html = apply_filters($this->shortcode . '_table_html', $html); 
  378.  
  379. if (false === $options['linkify'] || 'no' === strtolower($options['linkify'])) { 
  380. return $html; 
  381. } else { 
  382. return make_clickable($html); 
  383.  
  384. private function evenOrOdd ($x) { 
  385. return ((int) $x % 2) ? 'odd' : 'even'; // cast to integer just in case 
  386.  
  387. /** 
  388. * Simple CSV parsing, taken directly from PHP manual. 
  389. * @see http://www.php.net/manual/en/function.str-getcsv.php#100579 
  390. */ 
  391. private function str_getcsv ($input, $delimiter=', ', $enclosure='"', $escape=null, $eol=null) { 
  392. $temp=fopen("php://memory", "rw"); 
  393. fwrite($temp, $input); 
  394. fseek($temp, 0); 
  395. $r = array(); 
  396. while (($data = fgetcsv($temp, 4096, $delimiter, $enclosure)) !== false) { 
  397. $r[] = $data; 
  398. fclose($temp); 
  399. return $r; 
  400.  
  401. public function oEmbedHandler ($matches, $attr, $url, $rawattr) { 
  402. return $this->displayShortcode(array('key' => $url)); 
  403.  
  404. public function maybeFetchGvizDataSource () { 
  405. if ( 
  406. !isset($_GET[$this->prefix . 'get_datasource_nonce']) 
  407. || 
  408. !$this->isValidNonce($_GET[$this->prefix . 'get_datasource_nonce'], $this->prefix . 'get_datasource_nonce') 
  409. ) { return; } 
  410. $url = rawurldecode($_GET['url']); 
  411. $http_response = $this->doHttpRequest($url, false); 
  412.  
  413. if (isset($_GET['chart'])) { 
  414. $http_response['body'] = $this->setGVizCsvDataTypes($http_response['body']); 
  415.  
  416. require_once dirname(__FILE__) . '/lib/vistable.php'; 
  417. $vt = new csv_vistable( 
  418. (isset($_GET['tqx'])) ? $_GET['tqx'] : '',  
  419. (isset($_GET['tq'])) ? $_GET['tq'] : '',  
  420. (isset($_GET['tqrt'])) ? $_GET['tqrt']: '',  
  421. (isset($_GET['tz'])) ? $_GET['tz'] : 'PDT', // TODO: will get_option('timezone_string') work? 
  422. get_locale(),  
  423. array() 
  424. ); 
  425. $vt->setup_table($http_response['body']); 
  426. print @$vt->execute(); 
  427. exit; 
  428.  
  429. /** 
  430. * When trying to do a chart on standard CSV data,  
  431. * the vistable library needs help to hint at the 
  432. * data types of columns, or else it'll always treat 
  433. * the data as a string. 
  434. * @param string $csv_str The raw CSV data. 
  435. * @return string The same CSV data with a type-hinted header row. 
  436. */ 
  437. private function setGVizCsvDataTypes ($csv_str) { 
  438. $data = $this->parseCsv($csv_str); 
  439. $head = array_shift($data); 
  440. $cols = array(); 
  441. // peek at lines 2 through 20 (not the header) 
  442. $peek = (count($data) > 20) ? 20 : count($data); 
  443. for ($i = 0; $i < $peek; $i++) { 
  444. foreach ($data[$i] as $k => $v) { 
  445. if (ctype_digit($v) || preg_match('/^[0-9]+(?:\.[0-9]*)?$/', $v)) { 
  446. $cols[$k] = 'number'; 
  447. } else if (strtotime($v)) { 
  448. $cols[$k] = 'datetime'; 
  449. } else { 
  450. $cols[$k] = 'string'; 
  451. $head_typed = array(); 
  452. foreach ($head as $k => $v) { 
  453. if ('string' === $cols[$k]) { 
  454. $head_typed[] = $v; 
  455. } else { 
  456. $head_typed[] = $v . ' as ' . $cols[$k]; 
  457. array_unshift($data, $head_typed); 
  458. $lines = array(); 
  459. foreach ($data as $row) { 
  460. $lines[] = implode(', ', $row); 
  461. return implode("\n", $lines); 
  462.  
  463. private function makeNonceUrl ($url) { 
  464. $options = get_option($this->prefix . 'settings'); 
  465. $options[$this->prefix . 'get_datasource_nonce'] = wp_create_nonce($this->prefix . 'get_datasource_nonce'); 
  466. update_option($this->prefix . 'settings', $options); 
  467. $p = parse_url($url); 
  468. return $p['scheme'] 
  469. . '://' . $p['host'] . $p['path'] 
  470. . '?' . $p['query'] . '&' 
  471. . $this->prefix . 'get_datasource_nonce=' . $options[$this->prefix . 'get_datasource_nonce']; 
  472.  
  473. private function isValidNonce ($nonce, $nonce_name) { 
  474. $options = get_option($this->prefix . 'settings'); 
  475. $is_valid = ($nonce === $options[$nonce_name]) ? true : false; 
  476. unset($options[$this->prefix . 'get_datasource_nonce']); 
  477. update_option($this->prefix . 'settings', $options); 
  478. return $is_valid; 
  479.  
  480. /** 
  481. * WordPress Shortcode handler. 
  482. */ 
  483. public function displayShortcode ($atts, $content = null) { 
  484. $atts = shortcode_atts(array( 
  485. 'key' => false, // Google Doc URL or ID 
  486. 'title' => '', // Title (attribute) text or visible chart title 
  487. 'class' => '', // Container element's custom class value 
  488. 'gid' => false, // Sheet ID for a Google Spreadsheet, if only one 
  489. 'summary' => 'Google Spreadsheet', // If spreadsheet, value for summary attribute 
  490. 'width' => '100%',  
  491. 'height' => false,  
  492. 'style' => false,  
  493. 'strip' => 0, // If spreadsheet, how many rows to omit from top 
  494. 'header_rows' => 1, // Number of rows in <thead> 
  495. 'use_cache' => true, // Whether to use Transients API for fetched data. 
  496. 'http_opts' => false, // Arguments to pass to the WordPress HTTP API. 
  497. // TODO: Make a plugin option setting for default transient expiry time. 
  498. 'expire_in' => 10*MINUTE_IN_SECONDS, // Custom time-to-live of cached transient data. 
  499. 'lang' => get_bloginfo('language'),  
  500. 'linkify' => true, // Whether to run make_clickable() on parsed data. 
  501. 'query' => false, // Google Visualization Query Language querystring 
  502. 'chart' => false, // Type of Chart (for an interactive chart) 
  503.  
  504. // Depending on the type of chart, the following options may be available. 
  505. 'chart_aggregation_target' => false,  
  506. 'chart_annotations' => false,  
  507. 'chart_area_opacity' => false,  
  508. 'chart_axis_titles_position' => false,  
  509. 'chart_background_color' => false,  
  510. 'chart_bars' => false,  
  511. 'chart_bubble' => false,  
  512. 'chart_candlestick' => false,  
  513. 'chart_chart_area' => false,  
  514. 'chart_color_axis' => false,  
  515. 'chart_colors' => false,  
  516. 'chart_crosshair' => false,  
  517. 'chart_curve_type' => false,  
  518. 'chart_data_opacity' => false,  
  519. 'chart_dimensions' => false,  
  520. 'chart_enable_interactivity' => false,  
  521. 'chart_explorer' => false,  
  522. 'chart_focus_target' => false,  
  523. 'chart_font_name' => false,  
  524. 'chart_font_size' => false,  
  525. 'chart_force_i_frame' => false,  
  526. 'chart_h_axes' => false,  
  527. 'chart_h_axis' => false,  
  528. 'chart_height' => false,  
  529. 'chart_interpolate_nulls' => false,  
  530. 'chart_is_stacked' => false,  
  531. 'chart_legend' => false,  
  532. 'chart_line_width' => false,  
  533. 'chart_orientation' => false,  
  534. 'chart_pie_hole' => false,  
  535. 'chart_pie_residue_slice_color' => false,  
  536. 'chart_pie_residue_slice_label' => false,  
  537. 'chart_pie_slice_border_color' => false,  
  538. 'chart_pie_slice_text' => false,  
  539. 'chart_pie_slice_text_style' => false,  
  540. 'chart_pie_start_angle' => false,  
  541. 'chart_point_shape' => false,  
  542. 'chart_point_size' => false,  
  543. 'chart_reverse_categories' => false,  
  544. 'chart_selection_mode' => false,  
  545. 'chart_series' => false,  
  546. 'chart_size_axis' => false,  
  547. 'chart_slice_visibility_threshold' => false,  
  548. 'chart_slices' => false,  
  549. 'chart_theme' => false,  
  550. 'chart_title_position' => false,  
  551. 'chart_title_text_style' => false,  
  552. 'chart_tooltip' => false,  
  553. 'chart_trendlines' => false,  
  554. 'chart_v_axis' => false,  
  555. 'chart_width' => false,  
  556. // For some reason this isn't parsing? 
  557. //'chart_is3D' => false,  
  558.  
  559. // DataTables's HTML5 data- attributes. 
  560. // DataTables Features 
  561. // @see https://www.datatables.net/reference/option/#Features 
  562. 'datatables_auto_width' => false,  
  563. 'datatables_defer_render' => false,  
  564. 'datatables_info' => false,  
  565. 'datatables_j_query_UI' => false,  
  566. 'datatables_length_change' => false,  
  567. 'datatables_ordering' => false,  
  568. 'datatables_paging' => false,  
  569. 'datatables_processing' => false,  
  570. 'datatables_scroll_x' => false,  
  571. 'datatables_scroll_y' => false,  
  572. 'datatables_searching' => false,  
  573. 'datatables_server_side' => false,  
  574. 'datatables_state_save' => false,  
  575.  
  576. // DataTables Data 
  577. // @see https://www.datatables.net/reference/option/#Data 
  578. 'datatables_ajax' => false,  
  579. 'datatables_data' => false,  
  580.  
  581. // DataTables Options 
  582. // @see https://www.datatables.net/reference/option/#Options 
  583. 'datatables_defer_loading' => false,  
  584. 'datatables_destroy' => false,  
  585. 'datatables_display_start' => false,  
  586. 'datatables_dom' => false,  
  587. 'datatables_length_menu' => false,  
  588. 'datatables_order_cells_top' => false,  
  589. 'datatables_order_classes' => false,  
  590. 'datatables_order' => false,  
  591. 'datatables_order_fixed' => false,  
  592. 'datatables_order_multi' => false,  
  593. 'datatables_page_length' => false,  
  594. 'datatables_paging_type' => false,  
  595. 'datatables_renderer' => false,  
  596. 'datatables_retrieve' => false,  
  597. 'datatables_scroll_collapse' => false,  
  598. 'datatables_search_cols' => false,  
  599. 'datatables_search_delay' => false,  
  600. 'datatables_search' => false,  
  601. 'datatables_state_duration' => false,  
  602. 'datatables_stripe_classes' => false,  
  603. 'datatables_tab_index' => false,  
  604.  
  605. // DataTables Columns 
  606. // @see https://www.datatables.net/reference/option/#Columnes 
  607. 'datatables_column_defs' => false,  
  608. 'datatables_columns' => false,  
  609.  
  610. // DataTables extensions 
  611. // TableTools 
  612. // @see https://www.datatables.net/extensions/tabletools/initialisation 
  613. 'datatables_table_tools' => false 
  614. ), $atts, $this->shortcode); 
  615.  
  616. $atts['key'] = $this->sanitizeKey($atts['key']); 
  617. $atts['query'] = apply_filters($this->shortcode . '_query', $this->sanitizeQuery($atts['query']), $atts); 
  618.  
  619. try { 
  620. switch ($this->getDocTypeByKey($atts['key'])) { 
  621. case 'wpdb': 
  622. case 'mysql': 
  623. $output = $this->getSqlOutput($atts, $content); 
  624. break; 
  625. default: 
  626. $output = $this->getHttpOutput($atts, $content); 
  627. break; 
  628. } catch (Exception $e) { 
  629. $output = $e->getMessage(); 
  630. $this->invocations++; 
  631. return $output; 
  632.  
  633. /** 
  634. * Returns the output of an HTTP datasource. 
  635. * @param array $x The shortcode attributes. 
  636. * @param string $content The content of the shortcode. 
  637. * @return string The HTML output as requested by the shortcode or an error message. 
  638. */ 
  639. private function getHttpOutput ($x, $content) { 
  640. // Set up datasource URL. 
  641. $url = $x['key']; // in the default case, the URL is the shortcode's key 
  642. $key_type = $this->getDocTypeByKey($x['key']); 
  643. if ('spreadsheet' === $key_type) { 
  644. // if a Google Spreadsheet, the URL to fetch needs to be modified. 
  645. $url = $this->getSpreadsheetUrl($x); 
  646. } else { 
  647. if (!empty($x['chart'])) { $fmt = 'json'; } 
  648. else { $fmt = 'csv'; } 
  649. // the url should be proxied through this plugin 
  650. $url = $this->makeNonceUrl($this->getGVizDataSourceUrl($x['key'], $x['query'], $fmt)); 
  651.  
  652. // Retrieve and set HTML output. 
  653. if ('docsviewer' === $key_type) { 
  654. $output = $this->getGDocsViewerOutput($x); 
  655. } else { 
  656. if (false === $x['chart']) { 
  657. $http_response = $this->fetchData($url, $x); 
  658. $http_content_type = explode(';', $http_response['headers']['content-type']); 
  659. switch ($http_content_type[0]) { 
  660. case 'text/csv': 
  661. // This catches any HTTP response served as text/csv 
  662. $output = $this->csvToDataTable($http_response['body'], $x, $content); 
  663. break; 
  664. default: 
  665. $output = apply_filters($this->shortcode . '_webapp_html', $http_response['body'], $x); 
  666. if ('csv' === $key_type) { 
  667. // even if the response is text/plain, parse as CSV if the filename 
  668. // we detected earlier (by using the key attribute) suggests it is. 
  669. $output = $this->csvToDataTable($output, $x, $content); 
  670. break; 
  671. } else { 
  672. $output = $this->getGVizChartOutput($url, $x); 
  673. return $output; 
  674.  
  675. /** 
  676. * Returns the output of a SQL datasource. 
  677. * @param array $atts The shortcode attributes. 
  678. * @param string $content The content of the shortcode. 
  679. * @return string The HTML output as requested by the shortcode or an error message. 
  680. */ 
  681. private function getSqlOutput ($atts, $content) { 
  682. if (!$this->isSqlDbEnabled()) { 
  683. $this->runtimeError( 
  684. esc_html__('Error:', 'inline-gdocs-viewer') . ' ' 
  685. . esc_html__('SQL datasources are disabled.', 'inline-gdocs-viewer') 
  686. ); 
  687. if (!$this->canQuerySqlDatabases()) { 
  688. $this->runtimeError( 
  689. esc_html__('Error:', 'inline-gdocs-viewer') . ' ' 
  690. . esc_html__('The author does not have permission to perform a SQL query.', 'inline-gdocs-viewer') 
  691. ); 
  692.  
  693. $query = trim($atts['query']); 
  694. if (empty($query)) { 
  695. $this->runtimeError( 
  696. esc_html__('Error:', 'inline-gdocs-viewer') . ' ' 
  697. . esc_html__('Missing query.', 'inline-gdocs-viewer') 
  698. ); 
  699.  
  700. if (0 !== strpos(strtoupper($query), 'SELECT')) { 
  701. $this->runtimeError( 
  702. esc_html__('Error:', 'inline-gdocs-viewer') . ' ' 
  703. . esc_html__('Unsupported query:', 'inline-gdocs-viewer') 
  704. . ' ' . esc_html($query) 
  705. ); 
  706.  
  707. if ('wpdb' === $this->getDocTypeByKey($atts['key'])) { 
  708. global $wpdb; 
  709. } else { 
  710. $p = parse_url($atts['key']); 
  711. $wpdb = new wpdb( 
  712. isset($p['user']) ? $p['user'] : '',  
  713. isset($p['pass']) ? $p['pass'] : '',  
  714. isset($p['path']) ? basename($p['path']) : '',  
  715. isset($p['port']) ? "{$p['host']}:{$p['port']}" : $p['host'] 
  716. ); 
  717. $data = $wpdb->get_results($query, ARRAY_A); 
  718. if (empty($data)) { 
  719. $this->runtimeError( 
  720. esc_html__('Error:', 'inline-gdocs-viewer') . ' ' 
  721. . esc_html__('Query produced zero results:', 'inline-gdocs-viewer') 
  722. . ' ' . esc_html($query) 
  723. ); 
  724. $header = array(array()); // 2D 
  725. foreach ($data[0] as $k => $v) { 
  726. $header[0][] = $k; 
  727. $output = $this->dataToHtml(array_merge($header, $data), $atts, $content); 
  728. return $output; 
  729.  
  730. /** 
  731. * Whether or not the "Allow SQL queries in shortcodes" option is enabled. 
  732. * @return bool 
  733. */ 
  734. private function isSqlDbEnabled () { 
  735. $options = get_option($this->prefix . 'settings'); 
  736. return isset($options['allow_sql_db_queries']); 
  737. /** 
  738. * Determines if a user has the required capability to run a SQL query from the shortcode. 
  739. * @return bool Whether or not the author of the current post can do SQL queries. 
  740. */ 
  741. private function canQuerySqlDatabases () { 
  742. $author = get_userdata(get_the_author_meta('ID')); 
  743. return $author->has_cap($this->prefix . 'query_sql_databases'); 
  744.  
  745. /** 
  746. * WordPress mangles some HTML in subtle ways. Clean that up. 
  747. * @param string $key The value passed to the shortcode's `key` attribute. 
  748. * @return string The "sanitized" key value. 
  749. */ 
  750. private function sanitizeKey ($key) { 
  751. return str_replace('&', '&', $key); 
  752.  
  753. public function csvToDataTable ($csv, $x, $content) { 
  754. $data = $this->parseCsv($csv); 
  755. return $this->dataToHtml($data, $x, $content); 
  756.  
  757. private function getGDocsViewerOutput ($x) { 
  758. $output = '<iframe src="'; 
  759. $output .= esc_attr('https://docs.google.com/viewer?url=' . esc_url($x['key']) . '&embedded=true'); 
  760. $output .= '" width="' . esc_attr($x['width']) . '" height="' . esc_attr($x['height']) . '" style="' . esc_attr($x['style']) . '">'; 
  761. $output .= esc_html__('Your Web browser must support inline frames to display this content:', 'inline-gdocs-viewer'); 
  762. $output .= ' <a href="' . esc_attr($x['key']) . '">' . esc_html($x['title']) . '</a>'; 
  763. $output .= '</iframe>'; 
  764. return apply_filters($this->shortcode . '_viewer_html', $output); 
  765.  
  766. public function getGVizChartOutput ($url, $x) { 
  767. $chart_id = 'igsv-' . $this->invocations . '-' . $x['chart'] . 'chart-' . $this->getDocId($x['key']); 
  768. $output = '<div id="' . esc_attr($chart_id) . '" class="igsv-chart" title="' . esc_attr($x['title']) . '"'; 
  769. $output .= ' data-chart-type="' . esc_attr(ucfirst($x['chart'])) . '"'; 
  770. $output .= ' data-datasource-href="' . esc_attr($url) . '&chart=true"'; 
  771. if ($chart_opts = $this->getChartOptions($x)) { 
  772. foreach ($chart_opts as $k => $v) { 
  773. if (!empty($v)) { 
  774. // use single-quoted attribute-value syntax for later JSON parsing in JavaScript 
  775. $output .= ' data-' . str_replace('_', '-', $k) . "='" . $v . "'"; 
  776. $output .= '></div>'; // .igsv-chart 
  777. return $output; 
  778.  
  779. /** 
  780. * Retrieves the global plugin options. 
  781. * @return array An array of data suitable for passing to wp_localize_script(). 
  782. * @see https://codex.wordpress.org/Function_Reference/wp_localize_script 
  783. */ 
  784. private function getLocalizedPluginVars () { 
  785. $options = get_option($this->prefix . 'settings'); 
  786. $tmp = array(); 
  787. foreach (explode(' ', $options['datatables_classes']) as $cls) { 
  788. $cls = (empty($cls)) ? $this->dt_class : $cls; 
  789. $tmp[] = ".$cls:not(.no-datatables)"; 
  790. $dt_classes = implode(', ', $tmp); 
  791. $data = array( 
  792. 'lang_dir' => plugins_url('languages', __FILE__),  
  793. 'datatables_classes' => $dt_classes,  
  794. 'datatables_defaults_object' => $options['datatables_defaults_object'] 
  795. ); 
  796. return $data; 
  797.  
  798. private function getChartOptions($atts) { 
  799. $opts = array(); 
  800. foreach ($atts as $k => $v) { 
  801. if (0 === strpos($k, 'chart_')) { 
  802. $opts[$k] = $v; 
  803. return $opts; 
  804.  
  805. public function addQuickTagButton () { 
  806. $screen = get_current_screen(); 
  807. if (wp_script_is('quicktags') && 'post' === $screen->base) { 
  808. ?> 
  809. <script type="text/javascript"> 
  810. jQuery(function () { 
  811. var IGSV_QT = {}; 
  812. IGSV_QT.d = jQuery('#qt_content_igsv_dialog'); 
  813. IGSV_QT.d.find('#qt_content_igsv_dialog_tabs_container').tabs({ 
  814. // 'beforeActivate': function (e, ui) { 
  815. // if ('igsv-datasource-tab' === ui.newTab.context.getAttribute('id')) { return; } 
  816. // var is_valid = IGSV_QT.d.find('form').get(0).checkValidity(); 
  817. // if (!is_valid) { 
  818. // IGSV_QT.d.find('[name]:invalid').each(function () { 
  819. // jQuery(this).tooltip(); 
  820. // }); 
  821. // IGSV_QT.d.find('form :invalid:first-of-type').focus(); 
  822. // } else { 
  823. // IGSV_QT.d.find('[name]').each(function () { 
  824. // var x = jQuery(this).tooltip('instance'); 
  825. // if (x) { x.destroy(); } 
  826. // }) 
  827. // 
  828. // } 
  829. // return is_valid; 
  830. // } 
  831. }); 
  832. IGSV_QT.d.dialog({ 
  833. 'dialogClass' : 'wp-dialog',  
  834. 'modal' : true,  
  835. 'autoOpen' : false,  
  836. 'closeOnEscape': true,  
  837. 'minWidth' : 700,  
  838. 'buttons' : { 
  839. 'add' : { 
  840. 'text' : '<?php print esc_js(__('Add Spreadsheet', 'inline-gdocs-viewer'));?>',  
  841. 'class' : 'button-primary',  
  842. 'click' : function () { 
  843. var x = jQuery('#content').prop('selectionStart'); 
  844. var cur_txt = jQuery('#content').val(); 
  845. var new_txt = '[gdoc ' + shortcodeAttributes(getValues()) + ']'; 
  846. jQuery('#content').val([cur_txt.slice(0, x), new_txt, cur_txt.slice(x)].join('')); 
  847. resetDialogUi(); 
  848. jQuery(this).dialog('close'); 
  849.  
  850. function getValues () { 
  851. var atts = {}; 
  852. jQuery('#qt_content_igsv_dialog input[type="text"]').each(function () { 
  853. if (jQuery(this).val().length) { 
  854. atts[jQuery(this).attr('name')] = jQuery(this).val(); 
  855. }); 
  856. jQuery('#qt_content_igsv_dialog input[type="checkbox"]').each(function () { 
  857. if (false === jQuery(this).prop('checked')) { 
  858. atts[jQuery(this).attr('name')] = 'no'; 
  859. }); 
  860. return atts; 
  861. function shortcodeAttributes (atts) { 
  862. var str = ''; 
  863. for (k in atts) { 
  864. var v; 
  865. switch (k) { 
  866. case 'query': 
  867. v = atts[k] 
  868. .replace('<', encodeURIComponent('<')) 
  869. .replace('>', encodeURIComponent('>')); 
  870. break; 
  871. default: 
  872. atts[k] = v; 
  873. break; 
  874. str += ' ' + k + '="' + v + '"'; 
  875. return str; 
  876. function resetDialogUi () { 
  877. jQuery('#qt_content_igsv_dialog input').each(function () { 
  878. jQuery(this).val(''); 
  879. }); 
  880. }); 
  881. QTags.addButton( 
  882. 'igsv_sheet',  
  883. 'gdoc',  
  884. function () { 
  885. jQuery('#qt_content_igsv_sheet').on('click', function (e) { 
  886. e.preventDefault(); 
  887. IGSV_QT.d.dialog('open'); 
  888. IGSV_QT.d.find('input').get(0).focus(); 
  889. }); 
  890. jQuery('#qt_content_igsv_sheet').click(); 
  891. },  
  892. '[/gdoc]',  
  893. '',  
  894. '<?php print esc_js(__('Inline Google Spreadsheet shortcode', 'inline-gdocs-viewer'));?>',  
  895. 130 
  896. ); 
  897. }); 
  898. </script> 
  899. <div id="qt_content_igsv_dialog" title="<?php esc_attr_e('Insert Spreadsheet Data as a Table or Chart', 'inline-gdocs-viewer');?>"> 
  900. <div id="qt_content_igsv_dialog_tabs_container"> 
  901. <ul> 
  902. <li><a href="#igsv-datasource-tab"><?php esc_html_e('Datasource', 'inline-gdocs-viewer');?></a></li> 
  903. <li><a href="#igsv-integrations-tab"><?php esc_html_e('Integrations', 'inline-gdocs-viewer');?></a></li> 
  904. <li><a href="#igsv-extras-tab"><?php esc_html_e('Extras', 'inline-gdocs-viewer');?></a></li> 
  905. </ul> 
  906. <form> 
  907. <fieldset id="igsv-datasource-tab"> 
  908. <legend><?php esc_html_e('Datasource options', 'inline-gdocs-viewer');?></legend> 
  909. <p><?php esc_html_e('Provide a datasource. A datasource is usually a URL. If your data is in a Google Spreadsheet, paste the Web address of the spreadsheet. The only required attribute is the datasource "key." All other attributes are optional.', 'inline-gdocs-viewer');?> </p> 
  910. <table class="form-table" summary=""> 
  911. <tbody> 
  912. <tr> 
  913. <th> 
  914. <label for="js-qt-igsv-key"><?php esc_html_e('Key', 'inline-gdocs-viewer');?></label> 
  915. </th> 
  916. <td> 
  917. <input id="js-qt-igsv-key" name="key" type="text" placeholder="<?php esc_attr_e('paste your Google Spreadsheet or datasource URL here', 'inline-gdocs-viewer');?>" required="required" title="<?php esc_html_e('This attribute is required.', 'inline-gdocs-viewer');?>" /> 
  918. <p class="description"><?php esc_html_e('Paste the web address of your Google Spreadsheet or CSV-formatted data file.', 'inline-gdocs-viewer');?></p> 
  919. </td> 
  920. </tr> 
  921. <tr> 
  922. <th> 
  923. <label for="js-qt-igsv-query"><?php esc_html_e('Query', 'inline-gdocs-viewer');?></label> 
  924. </th> 
  925. <td> 
  926. <input id="js-qt-igsv-query" name="query" type="text" placeholder="<?php esc_attr_e('type a query', 'inline-gdocs-viewer');?>" /> 
  927. <p class="description"><?php esc_html_e('Enter a query to pre-process your data or to select only the parts of the data you want to use in your post.', 'inline-gdocs-viewer');?></p> 
  928. </td> 
  929. </tr> 
  930. <tr> 
  931. <th> 
  932. <label for="js-qt-igsv-title"><?php esc_html_e('Title', 'inline-gdocs-viewer');?></label> 
  933. </th> 
  934. <td> 
  935. <input id="js-qt-igsv-title" name="title" type="text" placeholder="<?php esc_attr_e('my data', 'inline-gdocs-viewer');?>" /> 
  936. <p class="description"><?php esc_html_e('A title usually appears as a tooltip when a user hovers their cursor over a table or is shown as the headline of a chart.', 'inline-gdocs-viewer');?></p> 
  937. </td> 
  938. </tr> 
  939. </tbody> 
  940. </table> 
  941. </fieldset> 
  942. <fieldset id="igsv-integrations-tab"> 
  943. <legend><?php esc_html_e('Integrations', 'inline-gdocs-viewer');?></legend> 
  944. <p><?php esc_html_e('If you use a custom theme or write custom functions, you can integrate your spreadsheet table or chart by specifying your integration values here. You can safely ignore these options if you do not have other code or are not using another plugin that needs them.', 'inline-gdocs-viewer');?> </p> 
  945. <table class="form-table" summary=""> 
  946. <tbody> 
  947. <tr> 
  948. <th> 
  949. <label for="js-qt-igsv-class"><?php esc_html_e('Class', 'inline-gdocs-viewer');?></label> 
  950. </th> 
  951. <td> 
  952. <input id="js-qt-igsv-class" name="class" type="text" placeholder="<?php esc_attr_e('custom-class other-custom-class', 'inline-gdocs-viewer');?>" /> 
  953. <p class="description"><?php esc_html_e('Add a custom HTML class value to the containing element.', 'inline-gdocs-viewer');?></p> 
  954. </td> 
  955. </tr> 
  956. <tr> 
  957. <th> 
  958. <label for="js-qt-igsv-style"><?php esc_html_e('Style', 'inline-gdocs-viewer');?></label> 
  959. </th> 
  960. <td> 
  961. <input id="js-qt-igsv-style" name="style" type="text" placeholder="<?php esc_attr_e('style', 'inline-gdocs-viewer');?>" /> 
  962. <p class="description"><?php esc_html_e('Add inline CSS rules to your table or chart if you need to tweak its appearance.', 'inline-gdocs-viewer');?></p> 
  963. </td> 
  964. </tr> 
  965. <tr> 
  966. <th> 
  967. <label for="js-qt-igsv-width"><?php esc_html_e('Width', 'inline-gdocs-viewer');?></label> 
  968. </th> 
  969. <td> 
  970. <input id="js-qt-igsv-width" name="width" type="text" placeholder="<?php esc_attr_e('width', 'inline-gdocs-viewer');?>" /> 
  971. <p class="description"><?php esc_html_e('Add an explicit width to your chart if you need to tweak its appearance. (Tables ignore this. Use the "style" option instead.)', 'inline-gdocs-viewer');?></p> 
  972. </td> 
  973. </tr> 
  974. <tr> 
  975. <th> 
  976. <label for="js-qt-igsv-height"><?php esc_html_e('Height', 'inline-gdocs-viewer');?></label> 
  977. </th> 
  978. <td> 
  979. <input id="js-qt-igsv-height" name="height" type="text" placeholder="<?php esc_attr_e('height', 'inline-gdocs-viewer');?>" /> 
  980. <p class="description"><?php esc_html_e('Add an explicit height to your chart if you need to tweak its appearance. (Tables ignore this. Use the "style" option instead.)', 'inline-gdocs-viewer');?></p> 
  981. </td> 
  982. </tr> 
  983. <tr> 
  984. <th> 
  985. <label for="js-qt-igsv-lang"><?php esc_html_e('Language', 'inline-gdocs-viewer');?></label> 
  986. </th> 
  987. <td> 
  988. <input id="js-qt-igsv-lang" name="lang" type="text" placeholder="<?php esc_attr_e('lang', 'inline-gdocs-viewer');?>" /> 
  989. <p class="description"><?php print sprintf( 
  990. esc_html__('If your datasource content is in a language other than %1$s, enter the %2$sISO-639%3$s language code for that language here.', 'inline-gdocs-viewer'),  
  991. '<code>' . get_locale() . '</code>',  
  992. '<a href="http://www.iso.org/iso/home/standards/language_codes.htm">', '</a>' 
  993. );?></p> 
  994. </td> 
  995. </tr> 
  996. </tbody> 
  997. </table> 
  998. </fieldset> 
  999. <fieldset id="igsv-extras-tab"> 
  1000. <legend><?php esc_html_e('Extras', 'inline-gdocs-viewer');?></legend> 
  1001. <table class="form-table" summary=""> 
  1002. <tbody> 
  1003. <tr> 
  1004. <th> 
  1005. <label for="js-qt-igsv-use-cache"><?php esc_html_e('Cache', 'inline-gdocs-viewer');?></label> 
  1006. </th> 
  1007. <td> 
  1008. <input id="js-qt-igsv-use-cache" name="use_cache" type="checkbox" checked="checked" /> 
  1009. <span class="description"><?php esc_html_e('To improve performance, your datasource is cached for ten minutes. If you are making many changes quickly, or if your spreadsheet data is small but frequently updated, you may want to disable caching. Disabling the cache is not recommended for medium or large datasets.', 'inline-gdocs-viewer');?></span> 
  1010. </td> 
  1011. </tr> 
  1012. <tr> 
  1013. <th> 
  1014. <label for="js-qt-igsv-linkify"><?php esc_html_e('Linkify', 'inline-gdocs-viewer');?></label> 
  1015. </th> 
  1016. <td> 
  1017. <input id="js-qt-igsv-linkify" name="linkify" type="checkbox" checked="checked" /> 
  1018. <span class="description"><?php esc_html_e('Email addresses and URLs in your data are automatically turned into clickable links. If this causes problems, you can disable the automatic linking feature by unchecking this box.', 'inline-gdocs-viewer');?></span> 
  1019. </td> 
  1020. </tr> 
  1021. </tbody> 
  1022. </table> 
  1023. </fieldset> 
  1024. </form> 
  1025. </div><!-- #qt_content_igsv_dialog_tabs_container --> 
  1026. <?php print $this->showDonationAppeal();?> 
  1027. </div><!-- #qt_content_igsv_dialog --> 
  1028. <?php 
  1029.  
  1030. private function registerContextualHelp () { 
  1031. $screen = get_current_screen(); 
  1032. if ($screen->id !== 'post' ) { return; } 
  1033.  
  1034. $html = '<p>'; 
  1035. $html .= sprintf( 
  1036. esc_html__('You can insert a Google Spreadsheet in this post. To do so, type %s[gdoc key="YOUR_SPREADSHEET_URL"]%s wherever you would like the spreadsheet to appear. Remember to replace YOUR_SPREADSHEET_URL with the web address of your Google Spreadsheet.', 'inline-gdocs-viewer'),  
  1037. '<kbd>', '</kbd>' 
  1038. ); 
  1039. $html .= '</p>'; 
  1040. $html .= '<p>'; 
  1041. $html .= esc_html__('Only Google Spreadsheets that have been shared using either the "Public on the web" or "anyone with the link" options will be visible on this page.', 'inline-gdocs-viewer'); 
  1042. $html .= '</p>'; 
  1043. $html .= '<p>' . sprintf( 
  1044. esc_html__('You can also transform your data into an interactive chart by using the %1$schart%2$s attribute. Supported chart types are Area, Bar, Bubble, Candlestick, Column, Combo, Histogram, Line, Pie, Scatter, and Stepped. For instance, to make a Pie chart, type %1$s[gdoc key="YOUR_SPREADSHEET_URL" chart="Pie"]%2$s. Customize your chart with your own choice of colors by supplying a space-separated list of color values with the %1$schart_colors%2$s attribute, like %1$schart_colors="red green"%2$s. Additional options depend on the chart you use.' , 'inline-gdocs-viewer'),  
  1045. '<kbd>', '</kbd>' 
  1046. ) . '</p>'; 
  1047. $html .= '<p>' . sprintf( 
  1048. esc_html__('Refer to the %1$sshortcode attribute documentation%3$s for a complete list of shortcode attributes, and the %2$sGoogle Chart API documentation%3$s for more information about each option.' , 'inline-gdocs-viewer'),  
  1049. '<a href="https://wordpress.org/plugins/inline-google-spreadsheet-viewer/other_notes/" target="_blank">',  
  1050. '<a href="https://developers.google.com/chart/interactive/docs/gallery" target="_blank">', '</a>' 
  1051. ) . '</p>'; 
  1052. $html .= '<p>'; 
  1053. $html .= sprintf( 
  1054. esc_html__('If you are having trouble getting your Spreadsheet to show up on your website, you can %sget help from the plugin support forum%s. Consider searching the support forum to see if your question has already been answered before posting a new thread.', 'inline-gdocs-viewer'),  
  1055. '<a href="https://wordpress.org/support/plugin/inline-google-spreadsheet-viewer/">', '</a>' 
  1056. ); 
  1057. $html .= '</p>'; 
  1058. ob_start(); 
  1059. $this->showDonationAppeal(); 
  1060. $x = ob_get_contents(); 
  1061. ob_end_clean(); 
  1062. $html .= $x; 
  1063. $screen->add_help_tab(array( 
  1064. 'id' => $this->shortcode . '-' . $screen->base . '-help',  
  1065. 'title' => __('Inserting a Google Spreadsheet', 'inline-gdocs-viewer'),  
  1066. 'content' => $html 
  1067. )); 
  1068.  
  1069. private function showDonationAppeal () { 
  1070. ?> 
  1071. <div class="donation-appeal"> 
  1072. <p style="text-align: center; font-style: italic; margin: 1em 3em;"><?php print sprintf( 
  1073. esc_html__('Inline Google Spreadsheet Viewer is provided as free software, but sadly grocery stores do not offer free food. If you like this plugin, please consider %1$s to its %2$s. ♥ Thank you!', 'inline-gdocs-viewer'),  
  1074. '<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=meitarm%40gmail%2ecom&lc=US&item_name=Inline%20Google%20Spreadsheet%20Viewer%20WordPress%20Plugin&item_number=inline%2dgdocs%2dviewer&currency_code=USD&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted">' . esc_html__('making a donation', 'inline-gdocs-viewer') . '</a>',  
  1075. '<a href="http://Cyberbusking.org/">' . esc_html__('houseless, jobless, nomadic developer', 'inline-gdocs-viewer') . '</a>' 
  1076. );?></p> 
  1077. </div> 
  1078. <?php 
  1079.  
  1080. public function validateSettings ($input) { 
  1081. $safe_input = array(); 
  1082. foreach ($input as $k => $v) { 
  1083. switch ($k) { 
  1084. case 'allow_sql_db_queries': 
  1085. $safe_input[$k] = intval($v); 
  1086. break; 
  1087. case 'datatables_classes': 
  1088. if (empty($v)) { $v = $this->dt_class; } 
  1089. $safe_input[$k] = sanitize_text_field($v); 
  1090. break; 
  1091. case 'datatables_defaults_object': 
  1092. if (empty($v)) { $v = $this->dt_defaults; } 
  1093. $safe_input[$k] = json_decode($v); 
  1094. break; 
  1095. return $safe_input; 
  1096.  
  1097. public function renderOptionsPage () { 
  1098. if (!current_user_can('manage_options')) { 
  1099. wp_die(__('You do not have sufficient permissions to access this page.', 'inline-gdocs-viewer')); 
  1100. $options = get_option($this->prefix . 'settings'); 
  1101. $datatables_defaults_json = (defined('JSON_PRETTY_PRINT')) 
  1102. ? json_encode($options['datatables_defaults_object'], JSON_PRETTY_PRINT) 
  1103. : json_encode($options['datatables_defaults_object']); 
  1104. ?> 
  1105. <h2><?php esc_html_e('Inline Google Spreadsheet Viewer Settings', 'inline-gdocs-viewer');?></h2> 
  1106. <form method="post" action="options.php"> 
  1107. <?php settings_fields($this->prefix . 'settings');?> 
  1108. <fieldset><legend><?php esc_html_e('DataTables defaults', 'inline-gdocs-viewer');?></legend> 
  1109. <table class="form-table" summary="<?php esc_attr_e('Site-wide defaults for DataTables-enhanced tables.', 'inline-gdocs-viewer');?>"> 
  1110. <tbody> 
  1111. <tr> 
  1112. <th> 
  1113. <label for="<?php esc_attr_e($this->prefix);?>datatables_classes"><?php esc_html_e('DataTables classes', 'inline-gdocs-viewer');?></label> 
  1114. </th> 
  1115. <td> 
  1116. <input id="<?php esc_attr_e($this->prefix);?>datatables_classes" name="<?php esc_attr_e($this->prefix);?>settings[datatables_classes]" value="<?php esc_attr_e($options['datatables_classes'])?>" placeholder="<?php esc_attr_e('class-1 class-2', 'inline-gdocs-viewer')?>" /> 
  1117. <p class="description"> 
  1118. <?php print sprintf( 
  1119. esc_html__('A space-separated list of HTML %1$sclass%2$s values. %1$s<table>%2$s elements with these classes will automatically be enhanced with %3$sjQuery DataTables%4$s, unless the given table also has the special %1$sno-datatables%2$s class. Leave blank to use the plugin default.', 'inline-gdocs-viewer'),  
  1120. '<code>', '</code>',  
  1121. '<a href="https://datatables.net/">', '</a>' 
  1122. );?> 
  1123. </p> 
  1124. </td> 
  1125. </tr> 
  1126. <tr> 
  1127. <th> 
  1128. <label for="<?php esc_attr_e($this->prefix);?>datatables_defaults_object"><?php esc_html_e('DataTables defaults object', 'inline-gdocs-viewer');?></label> 
  1129. </th> 
  1130. <td> 
  1131. <textarea 
  1132. id="<?php esc_attr_e($this->prefix);?>datatables_defaults_object" 
  1133. name="<?php esc_attr_e($this->prefix);?>settings[datatables_defaults_object]" 
  1134. placeholder='{ "searching": false, "ordering": false }' 
  1135. style="width: 50%; min-height: 200px;" 
  1136. ><?php if (!empty($options['datatables_defaults_object'])) { print stripslashes($datatables_defaults_json); }?></textarea> 
  1137. <p class="description"><?php print sprintf( 
  1138. esc_html__('Define a DataTables defaults initialization object. This is useful if you wish to change the default DataTables enhancements for all affected tables on your site at once. All DataTables-enhanced tables will use the DataTables options configured here unless explicitly overriden in the shortcode, HTML, or JavaScript initialization for the given table, itself. To learn more, read the %1$sDataTables manual section on Setting defaults%2$s and refer to the %3$sdocumentation for shortcode attributes available via this plugin%2$s. Leave blank to use the plugin default.'),  
  1139. '<a href="https://datatables.net/manual/options#Setting-defaults">', '</a>',  
  1140. '<a href="https://wordpress.org/plugins/inline-google-spreadsheet-viewer/other_notes/">' 
  1141. );?></p> 
  1142. </td> 
  1143. </tr> 
  1144. </tbody> 
  1145. </table> 
  1146. </fieldset> 
  1147. <fieldset><legend><?php esc_html_e('Advanced options', 'inline-gdocs-viewer');?></legend> 
  1148. <table class="form-table" summary="<?php esc_attr_e('Advanced Options', 'inline-gdocs-viewer');?>"> 
  1149. <tbody> 
  1150. <tr> 
  1151. <th> 
  1152. <label for="<?php esc_attr_e($this->prefix);?>allow_sql_db_queries"><?php esc_html_e('Allow SQL queries in shortcodes?', 'inline-gdocs-viewer');?></label> 
  1153. </th> 
  1154. <td> 
  1155. <input type="checkbox" <?php if (isset($options['allow_sql_db_queries'])) : print 'checked="checked"'; endif; ?> value="1" id="<?php esc_attr_e($this->prefix);?>allow_sql_db_queries" name="<?php esc_attr_e($this->prefix);?>settings[allow_sql_db_queries]" /> 
  1156. <label for="<?php esc_attr_e($this->prefix);?>allow_sql_db_queries"><span class="description"><?php 
  1157. print sprintf( 
  1158. esc_html__('Enabling this option permits SQL queries against arbitrary MySQL databases to be inserted as part of a %1$s shortcode. This is useful but can also be easily abused, so it is disabled by default. Even once enabled, such queries will only work in posts whose author has been granted the %2$s capability. (Only Administrators have this capability by default.)', 'inline-gdocs-viewer'),  
  1159. $this->shortcode,  
  1160. "<code>{$this->prefix}query_sql_databases</code>" 
  1161. ); 
  1162. ?></span></label> 
  1163. </td> 
  1164. </td> 
  1165. </tr> 
  1166. </tbody> 
  1167. </table> 
  1168. </fieldset> 
  1169. <?php submit_button();?> 
  1170. </form> 
  1171. <?php 
  1172. $this->showDonationAppeal();