1<?php 2/** 3 * DokuWiki Plugin yql (Syntax Component) 4 * 5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 6 * @author Michael Hamann <michael@content-space.de> 7 */ 8 9// must be run within Dokuwiki 10if (!defined('DOKU_INC')) die(); 11 12if (!defined('DOKU_LF')) define('DOKU_LF', "\n"); 13if (!defined('DOKU_TAB')) define('DOKU_TAB', "\t"); 14if (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); 15 16/** 17 * The YQL syntax plugin 18 */ 19class syntax_plugin_yql extends DokuWiki_Syntax_Plugin { 20 /** 21 * Syntax Type 22 * @return string The type 23 */ 24 public function getType() { 25 return 'substition'; 26 } 27 28 /** 29 * Paragraph Type 30 * 31 * Defines how this syntax is handled regarding paragraphs: 32 * 'block' - Open paragraphs need to be closed before plugin output 33 * @return string The paragraph type 34 * @see Doku_Handler_Block 35 */ 36 public function getPType() { 37 return 'block'; 38 } 39 40 /** 41 * @return int The sort order 42 */ 43 public function getSort() { 44 return 120; 45 } 46 47 48 /** 49 * Connect the plugin to the parser modes 50 * 51 * @param string $mode The current mode 52 */ 53 public function connectTo($mode) { 54 $this->Lexer->addSpecialPattern('<YQL.*?>.*?<\/YQL>',$mode,'plugin_yql'); 55 } 56 57 /** 58 * Handler to prepare matched data for the rendering process 59 * 60 * @param string $match The text matched by the patterns 61 * @param int $state The lexer state for the match 62 * @param int $pos The character position of the matched text 63 * @param Doku_Handler $handler Reference to the Doku_Handler object 64 * @return array The data that shall be passed to render() 65 */ 66 public function handle($match, $state, $pos, Doku_Handler $handler){ 67 $data = array(); 68 preg_match('/<YQL ?(.*)>(.*)<\/YQL>/ms', $match, $components); 69 70 if ($components[1]) { // parse parameters 71 preg_match_all('/\s*(\S+)="([^"]*)"\s*/', $components[1], $params, PREG_SET_ORDER); 72 foreach ($params as $param) { 73 array_shift($param); 74 list($key, $value) = $param; 75 switch ($key) { 76 case 'refresh': 77 $data['refresh'] = (int)$value; 78 break; 79 case 'format': 80 $parts = explode('%%', $value); 81 foreach ($parts as $pos => $part) { 82 if ($pos % 2 == 0) { // the start and every second part is pure character data 83 $data['format'][] = $part; 84 } else { // this is the stuff inside %% %% 85 if (strpos($part, '|') !== FALSE) { // is this a link? 86 list($link, $title) = explode('|', $part, 2); 87 $data['format'][] = array($link => $title); 88 } else { // if not just store the name, we'll recognize that again because of the position 89 $data['format'][] = $part; 90 } 91 } 92 } 93 break; 94 case 'item_name': 95 $data['item_name'] = $value; 96 break; 97 } 98 } 99 } 100 101 $data['query'] = $components[2]; 102 // set default values 103 if (!isset($data['refresh'])) $data['refresh'] = 14400; 104 if (!isset($data['format'])) $data['format'] = array('', array('link' => 'title'), ''); 105 if (!isset($data['item_name'])) $data['item_name'] = 'item'; 106 107 return $data; 108 } 109 110 /** 111 * Handles the actual output creation. 112 * 113 * @param $mode string output format being rendered 114 * @param $renderer Doku_Renderer reference to the current renderer object 115 * @param $data array data created by handler() 116 * @return boolean rendered correctly? 117 */ 118 public function render($mode, Doku_Renderer $renderer, $data) { 119 $refresh = $data['refresh']; 120 $format = $data['format']; 121 $item_name = $data['item_name']; 122 $query = $data['query']; 123 124 // Don't fetch the data for rendering metadata 125 // But still do it for all other modes in order to support different renderers 126 if ($mode == 'metadata') { 127 /** @var $renderer Doku_Renderer_metadata */ 128 $renderer->meta['date']['valid']['age'] = 129 isset($renderer->meta['date']['valid']['age']) ? 130 min($renderer->meta['date']['valid']['age'],$refresh) : 131 $refresh; 132 return true; 133 } 134 135 // execute the YQL query 136 137 $yql_base_url = "http://query.yahooapis.com/v1/public/yql"; 138 $yql_query_url = $yql_base_url . "?q=" . urlencode($query); 139 $yql_query_url .= "&format=json"; 140 $client = new DokuHTTPClient(); 141 $result = $client->sendRequest($yql_query_url); 142 143 if ($result === false) { 144 $this->render_error($renderer, 'YQL: Error: the request to the server failed: '.$client->error); 145 return true; 146 } 147 148 $json_parser = new JSON(); 149 $json_result = $json_parser->decode($client->resp_body); 150 151 // catch YQL errors 152 if (isset($json_result->error)) { 153 $this->render_error($renderer, 'YQL: YQL Error: '.$json_result->error->description); 154 return true; 155 } 156 157 if (is_null($json_result->query->results)) { 158 $this->render_error($renderer, 'YQL: Unknown error: there is neither an error nor results in the YQL result.'); 159 return true; 160 } 161 162 if (!isset($json_result->query->results->$item_name)) { 163 $this->render_error($renderer, 'YQL: Error: The item name '.$item_name.' doesn\'t exist in the results'); 164 return true; 165 } 166 167 $renderer->listu_open(); 168 foreach ($json_result->query->results->$item_name as $item) { 169 $renderer->listitem_open(1); 170 $renderer->listcontent_open(); 171 foreach ($format as $pos => $val) { 172 if ($pos % 2 == 0) { // outside %% %%, just character data 173 $renderer->cdata($val); 174 } else { // inside %% %%, either links or other fields 175 if (is_array($val)) { // arrays are links 176 foreach ($val as $link => $title) { 177 // check if there is a link at all and if the title isn't an instance of stdClass (can't be casted to string) 178 if (!isset($item->$link)) { 179 $this->render_error($renderer, 'YQL: Error: The given attribute '.$link.' doesn\'t exist'); 180 continue; 181 } 182 183 if (!isset($item->$title)) { 184 $this->render_error($renderer, 'YQL: Error: The given attribute '.$title.' doesn\'t exist'); 185 continue; 186 } 187 188 if ($item->$title instanceof stdClass) { 189 $this->render_error($renderer, 'YQL: Error: The given attribute '.$title.' is not a simple string but an object'); 190 continue; 191 } 192 193 // links can be objects, then they should have an attribute "href" which contains the actual url 194 if ($item->$link instanceof stdClass && !isset($item->$link->href)) { 195 $this->render_error($renderer, 'YQL: Error: The given attribute '.$link.' is not a simple string but also doesn\'t have a href attribute as link objects have.'); 196 continue; 197 } 198 199 if ($item->$link instanceof stdClass) { 200 $renderer->externallink($item->$link->href, (string)$item->$title); 201 } else { 202 $renderer->externallink($item->$link, (string)$item->$title); 203 } 204 } 205 } else { // just a field 206 // test if the value really exists and if isn't a stdClass (can't be casted to string) 207 if (!isset($item->$val)) { 208 $this->render_error($renderer, 'YQL: Error: The given attribute '.$val.' doesn\'t exist'); 209 continue; 210 } 211 212 if ($item->$val instanceof stdClass) { 213 $this->render_error($renderer, 'YQL: Error: The given attribute '.$val.' is not a simple string but an object'); 214 continue; 215 } 216 217 $renderer->cdata((string)$item->$val); 218 } 219 } 220 } 221 $renderer->listcontent_close(); 222 $renderer->listitem_close(); 223 } 224 $renderer->listu_close(); 225 226 return true; 227 } 228 229 /** 230 * Helper function for displaying error messages. Currently just adds a paragraph with emphasis and the error message in it 231 */ 232 private function render_error(Doku_Renderer $renderer, $error) { 233 $renderer->p_open(); 234 $renderer->emphasis_open(); 235 $renderer->cdata($error); 236 $renderer->emphasis_close(); 237 $renderer->p_close(); 238 } 239} 240 241// vim:ts=4:sw=4:et: 242