1<?php 2 3/** 4 * Module classes 5 * @package framework 6 * @subpackage module 7 */ 8 9/** 10 * Module data management. These functions provide an interface for modules (both handler and output) 11 * to fetch data set by other modules and to return their own output. Handler modules must use these 12 * methods to set a response, output modules must if the format is AJAX, otherwise they should return 13 * an HTML5 string 14 */ 15trait Hm_Module_Output { 16 17 /* module output */ 18 protected $output = array(); 19 20 /* protected output keys */ 21 protected $protected = array(); 22 23 /* list of appendable keys */ 24 protected $appendable = array(); 25 26 /** 27 * @param string $name name to check for 28 * @param array $list array to look for name in 29 * @param string $type 30 * @param mixed $value value 31 * @return bool 32 */ 33 protected function check_overwrite($name, $list, $type, $value) { 34 if (in_array($name, $list, true)) { 35 Hm_Debug::add(sprintf('MODULES: Cannot overwrite %s %s with %s', $type, $name, print_r($value,true))); 36 return false; 37 } 38 return true; 39 } 40 41 /** 42 * Add a name value pair to the output array 43 * @param string $name name of value to store 44 * @param mixed $value value 45 * @param bool $protected true disallows overwriting 46 * @return bool true on success 47 */ 48 public function out($name, $value, $protected=true) { 49 if (!$this->check_overwrite($name, $this->protected, 'protected', $value)) { 50 return false; 51 } 52 if (!$this->check_overwrite($name, $this->appendable, 'protected', $value)) { 53 return false; 54 } 55 if ($protected) { 56 $this->protected[] = $name; 57 } 58 $this->output[$name] = $value; 59 return true; 60 } 61 62 /** 63 * append a value to an array, create it if does not exist 64 * @param string $name array name 65 * @param string $value value to add 66 * @return bool true on success 67 */ 68 public function append($name, $value) { 69 if (!$this->check_overwrite($name, $this->protected, 'protected', $value)) { 70 return false; 71 } 72 if (array_key_exists($name, $this->output)) { 73 if (is_array($this->output[$name])) { 74 $this->output[$name][] = $value; 75 return true; 76 } 77 else { 78 Hm_Debug::add(sprintf('Tried to append %s to scaler %s', $value, $name)); 79 return false; 80 } 81 } 82 else { 83 $this->output[$name] = array($value); 84 $this->appendable[] = $name; 85 return true; 86 } 87 } 88 89 /** 90 * Sanitize input string 91 * @param string $string text to sanitize 92 * @param bool $special_only only use htmlspecialchars not htmlentities 93 * @return string sanitized value 94 */ 95 public function html_safe($string, $special_only=false) { 96 if ($special_only) { 97 return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, "UTF-8"); 98 } 99 return htmlentities($string, ENT_QUOTES | ENT_SUBSTITUTE, "UTF-8"); 100 } 101 102 /** 103 * Concatenate a value 104 * @param string $name name to add to 105 * @param string $value value to add 106 * @return bool true on success 107 */ 108 public function concat($name, $value) { 109 if (array_key_exists($name, $this->output)) { 110 if (is_string($this->output[$name])) { 111 $this->output[$name] .= $value; 112 return true; 113 } 114 else { 115 Hm_Debug::add(sprintf('Could not append %s to %s', print_r($value,true), $name)); 116 return false; 117 } 118 } 119 else { 120 $this->output[$name] = $value; 121 return true; 122 } 123 } 124 125 /** 126 * Return module output from process() 127 * @return array 128 */ 129 public function module_output() { 130 return $this->output; 131 } 132 133 /** 134 * Return protected output field list 135 * @return array 136 */ 137 public function output_protected() { 138 return $this->protected; 139 } 140 141 /** 142 * Fetch an output value 143 * @param string $name key to fetch the value for 144 * @param mixed $default default return value if not found 145 * @param string $typed if a default value is given, typecast the result to it's type 146 * @return mixed value if found or default 147 */ 148 public function get($name, $default=NULL, $typed=true) { 149 if (array_key_exists($name, $this->output)) { 150 $val = $this->output[$name]; 151 if (!is_null($default) && $typed) { 152 if (gettype($default) != gettype($val)) { 153 Hm_Debug::add(sprintf('TYPE CONVERSION: %s to %s for %s', gettype($val), gettype($default), $name)); 154 settype($val, gettype($default)); 155 } 156 } 157 return $val; 158 } 159 return $default; 160 } 161 162 /** 163 * Check for a key 164 * @param string $name key name 165 * @return bool true if found 166 */ 167 public function exists($name) { 168 return array_key_exists($name, $this->output); 169 } 170 171 /** 172 * Check to see if a value matches a list 173 * @param string $name name to check 174 * @param array $values list to check against 175 * @return bool true if found 176 */ 177 public function in($name, $values) { 178 if (array_key_exists($name, $this->output) && in_array($this->output[$name], $values, true)) { 179 return true; 180 } 181 return false; 182 } 183} 184 185/** 186 * Methods used to validate handler module operations, like the HTTP request 187 * type and target/origin values 188 */ 189trait Hm_Handler_Validate { 190 191 /** 192 * Validate HTTP request type, only GET and POST are allowed 193 * @param object $session 194 * @param object $request 195 * @return bool 196 */ 197 public function validate_method($session, $request) { 198 if (!in_array(strtolower($request->method), array('get', 'post'), true)) { 199 if ($session->loaded) { 200 $session->destroy($request); 201 Hm_Debug::add(sprintf('LOGGED OUT: invalid method %s', $request->method)); 202 } 203 return false; 204 } 205 return true; 206 } 207 208 /** 209 * Validate that the request has matching source and target origins 210 * @return bool 211 */ 212 public function validate_origin($session, $request, $config) { 213 if (!$session->loaded) { 214 return true; 215 } 216 list($source, $target) = $this->source_and_target($request, $config); 217 if (!$this->validate_target($target, $source, $session, $request) || 218 !$this->validate_source($target, $source, $session, $request)) { 219 return false; 220 } 221 return true; 222 } 223 224 /** 225 * Find source and target values for validate_origin 226 * @return string[] 227 */ 228 private function source_and_target($request, $config) { 229 $source = false; 230 $target = $config->get('cookie_domain', false); 231 if ($target == 'none') { 232 $target = false; 233 } 234 $server_vars = array( 235 'HTTP_REFERER' => 'source', 236 'HTTP_ORIGIN' => 'source', 237 'HTTP_HOST' => 'target', 238 'HTTP_X_FORWARDED_HOST' => 'target' 239 ); 240 foreach ($server_vars as $header => $type) { 241 if (array_key_exists($header, $request->server) && $request->server[$header]) { 242 $$type = $request->server[$header]; 243 } 244 } 245 return array($source, $target); 246 } 247 248 /** 249 * @param string $target 250 * @param string $source 251 * @return boolean 252 */ 253 private function validate_target($target, $source, $session, $request) { 254 if (!$target || !$source) { 255 $session->destroy($request); 256 Hm_Debug::add('LOGGED OUT: missing target origin'); 257 return false; 258 } 259 return true; 260 } 261 262 /** 263 * @param string $target 264 * @param string $source 265 * @return boolean 266 */ 267 private function validate_source($target, $source, $session, $request) { 268 $source = parse_url($source); 269 if (!is_array($source) || !array_key_exists('host', $source)) { 270 $session->destroy($request); 271 Hm_Debug::add('LOGGED OUT: invalid source origin'); 272 return false; 273 } 274 if (array_key_exists('port', $source)) { 275 $source['host'] .= ':'.$source['port']; 276 } 277 if ($source['host'] !== $target) { 278 $session->destroy($request); 279 Hm_Debug::add('LOGGED OUT: invalid source origin'); 280 return false; 281 } 282 return true; 283 } 284} 285 286/** 287 * Base class for data input processing modules, called "handler modules" 288 * 289 * All modules that deal with processing input data extend from this class. 290 * It provides access to input and state through the following member variables: 291 * 292 * $session The session interface object 293 * $request The HTTP request details object 294 * $config The site config object 295 * $user_config The user settings object for the current user 296 * 297 * Modules that extend this class need to override the process function 298 * Modules can pass information to the output modules using the out() and append() methods, 299 * and see data from other modules with the get() method 300 * @abstract 301 */ 302abstract class Hm_Handler_Module { 303 304 use Hm_Module_Output; 305 use Hm_Handler_Validate; 306 307 /* session object */ 308 public $session; 309 310 /* request object */ 311 public $request; 312 313 /* site configuration object */ 314 public $config; 315 316 /* current request id */ 317 protected $page = ''; 318 319 /* user settings */ 320 public $user_config; 321 322 public $cache; 323 /** 324 * Assign input and state sources 325 * @param object $parent instance of the Hm_Request_Handler class 326 * @param string $page page id 327 * @param array $output data from handler modules 328 * @param array $protected list of protected output names 329 */ 330 public function __construct($parent, $page, $output=array(), $protected=array()) { 331 $this->session = $parent->session; 332 $this->request = $parent->request; 333 $this->cache = $parent->cache; 334 $this->page = $page; 335 $this->config = $parent->site_config; 336 $this->user_config = $parent->user_config; 337 $this->output = $output; 338 $this->protected = $protected; 339 } 340 341 /** 342 * @return string 343 */ 344 private function invalid_ajax_key() { 345 if (DEBUG_MODE) { 346 Hm_Debug::add('REQUEST KEY check failed'); 347 Hm_Debug::load_page_stats(); 348 Hm_Debug::show(); 349 } 350 Hm_Functions::cease(json_encode(array('status' => 'not callable')));; 351 return 'exit'; 352 } 353 354 /** 355 * @return string 356 */ 357 private function invalid_http_key() { 358 if ($this->session->loaded) { 359 $this->session->destroy($this->request); 360 Hm_Debug::add('LOGGED OUT: request key check failed'); 361 } 362 Hm_Dispatch::page_redirect('?page=home'); 363 return 'redirect'; 364 } 365 366 /** 367 * Validate a form key. If this is a non-empty POST form from an 368 * HTTP request or AJAX update, it will take the user to the home 369 * page if the page_key value is either not present or not valid 370 * @return false|string 371 */ 372 public function process_key() { 373 if (empty($this->request->post)) { 374 return false; 375 } 376 $key = array_key_exists('hm_page_key', $this->request->post) ? $this->request->post['hm_page_key'] : false; 377 $valid = Hm_Request_Key::validate($key); 378 if ($valid) { 379 return false; 380 } 381 if ($this->request->type == 'AJAX') { 382 return $this->invalid_ajax_key(); 383 } 384 else { 385 return $this->invalid_http_key(); 386 } 387 } 388 389 /** 390 * Validate a value in a HTTP POST form 391 * @param mixed $val 392 * @return mixed 393 */ 394 private function check_field($val) { 395 switch (true) { 396 case is_array($val): 397 case trim($val) !== '': 398 case $val === '0': 399 case $val === 0: 400 return $val; 401 default: 402 return NULL; 403 } 404 } 405 406 /** 407 * Process an HTTP POST form 408 * @param array $form list of required field names in the form 409 * @return array tuple with a bool indicating success, and an array of valid form values 410 */ 411 public function process_form($form) { 412 $new_form = array(); 413 foreach($form as $name) { 414 if (!array_key_exists($name, $this->request->post)) { 415 continue; 416 } 417 $val = $this->check_field($this->request->post[$name]); 418 if ($val !== NULL) { 419 $new_form[$name] = $val; 420 } 421 } 422 return array((count($form) === count($new_form)), $new_form); 423 } 424 425 /** 426 * Determine if a module set is enabled 427 * @param string $name the module set name to check for 428 * @return bool 429 */ 430 public function module_is_supported($name) { 431 return in_array(strtolower($name), $this->config->get_modules(true), true); 432 } 433 434 /** 435 * Handler modules need to override this method to do work 436 */ 437 abstract public function process(); 438} 439 440/** 441 * Base class for output modules 442 * All modules that output data to a request must extend this class and define 443 * an output() method. It provides form validation, html sanitizing, 444 * and string translation services to modules 445 * @abstract 446 */ 447abstract class Hm_Output_Module { 448 449 use Hm_Module_Output; 450 451 /* translated language strings */ 452 protected $lstr = array(); 453 454 /* langauge name */ 455 protected $lang = false; 456 457 /* UI layout direction */ 458 protected $dir = 'ltr'; 459 460 /* Output format (AJAX or HTML5) */ 461 protected $format = ''; 462 463 /** 464 * Constructor 465 * @param array $input data from handler modules 466 * @param array $protected list of protected keys 467 */ 468 public function __construct($input, $protected) { 469 $this->output = $input; 470 $this->protected = $protected; 471 } 472 473 /** 474 * Return a translated string if possible 475 * @param string $string the string to be translated 476 * @return string translated string 477 */ 478 public function trans($string) { 479 if (array_key_exists($string, $this->lstr)) { 480 if ($this->lstr[$string] === false) { 481 return strip_tags($string); 482 } 483 else { 484 return strip_tags($this->lstr[$string]); 485 } 486 } 487 else { 488 Hm_Debug::add(sprintf('TRANSLATION NOT FOUND :%s:', $string)); 489 } 490 return str_replace('\n', '<br />', strip_tags($string)); 491 } 492 493 /** 494 * Build output by calling module specific output functions 495 * @param string $format output type, either HTML5 or AJAX 496 * @param array $lang_str list of language translation strings 497 * @return string 498 */ 499 public function output_content($format, $lang_str) { 500 $this->lstr = $lang_str; 501 $this->format = str_replace('Hm_Format_', '', $format); 502 if (array_key_exists('interface_lang', $lang_str)) { 503 $this->lang = $lang_str['interface_lang']; 504 } 505 if (array_key_exists('interface_direction', $lang_str)) { 506 $this->dir = $lang_str['interface_direction']; 507 } 508 return $this->output(); 509 } 510 511 /** 512 * Output modules need to override this method to add to a page or AJAX response 513 * @return string 514 */ 515 abstract protected function output(); 516} 517 518/** 519 * Placeholder classes for disabling a module in a set. These allow a module set 520 * to replace another module set's assignments with "false" to disable them 521 */ 522class Hm_Output_ extends Hm_Output_Module { protected function output() {} } 523class Hm_Handler_ extends Hm_Handler_Module { public function process() {} } 524