1: <?php
2:
3: /**
4: * Format output
5: * @package framework
6: * @subpackage format
7: */
8:
9: /**
10: * Base class for output formatting. Currently JSON and HTML5 formats are
11: * supported. To add support for a new format this class must be extended
12: * and the content method needs to be overridden.
13: * @abstract
14: */
15: abstract class HM_Format {
16:
17: protected $config;
18:
19: /**
20: * Init
21: * @param object $config site config object
22: */
23: public function __construct($config) {
24: $this->config = $config;
25: }
26:
27: /**
28: * Return combined output from all modules. Must be overridden by specific
29: * output classes
30: * @param array $allowed_output allowed fields for JSON responses
31: * @return mixed combined output
32: */
33: abstract protected function content($input, $allowed_output);
34: }
35:
36: /**
37: * Handles JSON formatted results for AJAX requests
38: */
39: class Hm_Format_JSON extends HM_Format {
40:
41: /**
42: * Run modules and merge + filter the result array
43: * @param array $output data from the handler modules
44: * @param array $allowed_output allowed fields for JSON responses
45: * @return string encoded data to be sent to the browser
46: */
47: public function content($output, $allowed_output) {
48: $output['router_user_msgs'] = Hm_Msgs::getRaw();
49: $output = $this->filter_all_output($output, $allowed_output);
50: if ($this->config->get('encrypt_ajax_requests', false)) {
51: $output = ['payload' => Hm_Crypt_Base::ciphertext(json_encode($output, JSON_FORCE_OBJECT), Hm_Request_Key::generate())];
52: }
53: return json_encode($output, JSON_FORCE_OBJECT);
54: }
55:
56: /**
57: * Filter data against module set white lists before sending it to the browser
58: * @param array $data output module data to filter
59: * @param array $allowed set of white list filters
60: * @return array filtered data
61: */
62: public function filter_all_output($data, $allowed) {
63: foreach ($data as $name => $value) {
64: if (!array_key_exists($name, $allowed)) {
65: unset($data[$name]);
66: continue;
67: }
68: $new_value = $this->filter_output($name, $value, $allowed);
69: if ($new_value === NULL) {
70: unset($data[$name]);
71: continue;
72: }
73: $data[$name] = $new_value;
74: }
75: return $data;
76: }
77:
78: /**
79: * Filter a single output value
80: * @param string $name
81: * @param mixed $value
82: * @param array $allowed
83: * @return mixed
84: */
85: private function filter_output($name, $value, $allowed) {
86: if ($allowed[$name][1]) {
87: $new_value = filter_var($value, $allowed[$name][0], $allowed[$name][1]);
88: } else {
89: $new_value = filter_var($value, $allowed[$name][0]);
90: }
91: if ($new_value === false && $allowed[$name] != FILTER_VALIDATE_BOOLEAN) {
92: return NULL;
93: }
94: return $new_value;
95: }
96: }
97:
98: /**
99: * Handles HTML5 formatted results for normal HTTP requests
100: */
101: class Hm_Format_HTML5 extends HM_Format {
102:
103: /**
104: * Collect and return content from modules for HTTP requests
105: * @param array $output data from the output modules
106: * @param array $allowed_output allowed fields for JSON responses
107: * @return string HTML5 content
108: */
109: public function content($output, $allowed_output) {
110: if (array_key_exists('router_module_list', $output)) {
111: unset($output['router_module_list']);
112: }
113: return implode('', $output);
114: }
115: }
116:
117: /**
118: * binary safe wrapper around json encode/decode using base64
119: */
120: class Hm_Transform {
121:
122: /**
123: * Convert an array to a string
124: * @param mixed $data data to be transformed to a string
125: * @param string $encoding encoding to use for values
126: * @return string on success, false on failure
127: */
128: public static function stringify($data, $encoding = 'base64_encode') {
129: if (is_string($data)) {
130: return $data;
131: }
132: if (!is_array($data)) {
133: return (string) $data;
134: }
135: return @json_encode(self::hm_encode($data, $encoding));
136:
137: }
138:
139: /**
140: * @param string $data
141: * @return array|false
142: */
143: public static function convert($data) {
144: if (mb_substr($data, 0, 2) === 'a:') {
145: return @unserialize($data);
146: } elseif (mb_substr($data, 0, 1) === '{' || mb_substr($data, 0, 1) === '[') {
147: return @json_decode($data, true);
148: }
149: return false;
150: }
151:
152: /**
153: * Convert a stringified array back to an array
154: * @param string|false $data data to be transformed from a string
155: * @param string $encoding encoding to use for values
156: * @param boolean $return return original string if true
157: * @return mixed array on success, false or original string on failure
158: */
159: public static function unstringify($data, $encoding = 'base64_decode', $return = false) {
160: if (!is_string($data) || !trim($data)) {
161: return false;
162: }
163: $result = self::convert($data);
164: if (is_array($result)) {
165: return self::hm_encode($result, $encoding);
166: }
167: if ($return) {
168: return $data;
169: }
170: return false;
171: }
172:
173: /**
174: * Recursively encode values in an array
175: * @param array $data data to encode values for
176: * @param string $encoding the type of encoding to use
177: * @return array
178: */
179: public static function hm_encode($data, $encoding) {
180: $result = [];
181: foreach ($data as $name => $val) {
182: if (is_array($val)) {
183: $result[$name] = self::hm_encode($val, $encoding);
184: } else {
185: if (is_string($val)) {
186: $result[$name] = $encoding($val);
187: } else {
188: $result[$name] = $val;
189: }
190: }
191: }
192: return $result;
193: }
194: }
195: