Merge pull request #19152 from MegaphoneJon/financial-160
[civicrm-core.git] / CRM / Utils / API / HTMLInputCoder.php
CommitLineData
a9396478
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
bc77d7c0 4 | Copyright CiviCRM LLC. All rights reserved. |
a9396478 5 | |
bc77d7c0
TO
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
a9396478 9 +--------------------------------------------------------------------+
d25dd0ee 10 */
a9396478
TO
11
12/**
13 * This class captures the encoding practices of CRM-5667 in a reusable
14 * fashion. In this design, all submitted values are partially HTML-encoded
15 * before saving to the database. If a DB reader needs to output in
16 * non-HTML medium, then it should undo the partial HTML encoding.
17 *
18 * This class should be short-lived -- 4.3 should introduce an alternative
19 * escaping scheme and consequently remove HTMLInputCoder.
20 *
21 * @package CRM
ca5cec67 22 * @copyright CiviCRM LLC https://civicrm.org/licensing
a9396478 23 */
a9396478
TO
24class CRM_Utils_API_HTMLInputCoder extends CRM_Utils_API_AbstractFieldCoder {
25 private $skipFields = NULL;
26
27 /**
28 * @var CRM_Utils_API_HTMLInputCoder
29 */
30 private static $_singleton = NULL;
31
32 /**
33 * @return CRM_Utils_API_HTMLInputCoder
34 */
35 public static function singleton() {
36 if (self::$_singleton === NULL) {
37 self::$_singleton = new CRM_Utils_API_HTMLInputCoder();
38 }
39 return self::$_singleton;
40 }
41
42 /**
3d469574 43 * Get skipped fields.
44 *
45 * @return array<string>
46 * list of field names
a9396478
TO
47 */
48 public function getSkipFields() {
49 if ($this->skipFields === NULL) {
be2fb01f 50 $this->skipFields = [
a9396478
TO
51 'widget_code',
52 'html_message',
53 'body_html',
54 'msg_html',
55 'description',
56 'intro',
57 'thankyou_text',
58 'tf_thankyou_text',
59 'intro_text',
60 'page_text',
61 'body_text',
62 'footer_text',
63 'thankyou_footer',
64 'thankyou_footer_text',
65 'new_text',
66 'renewal_text',
67 'help_pre',
68 'help_post',
69 'confirm_title',
70 'confirm_text',
71 'confirm_footer_text',
72 'confirm_email_text',
73 'event_full_text',
74 'waitlist_text',
75 'approval_req_text',
76 'report_header',
77 'report_footer',
78 'cc_id',
79 'bcc_id',
80 'premiums_intro_text',
81 'honor_block_text',
82 'pay_later_text',
83 'pay_later_receipt',
6714d8d2
SL
84 // This is needed for FROM Email Address configuration. dgg
85 'label',
86 // This is needed for navigation items urls
87 'url',
a9396478 88 'details',
6714d8d2
SL
89 // message templates’ text versions
90 'msg_text',
91 // (send an) email to contact’s and CiviMail’s text version
92 'text_message',
93 // data i/p of persistent table
94 'data',
95 // CRM-6673
96 'sqlQuery',
a9396478
TO
97 'pcp_title',
98 'pcp_intro_text',
6714d8d2
SL
99 // The 'new' text in word replacements
100 'new',
101 // e.g. '"Full Name" <user@example.org>'
102 'replyto_email',
a265eee1 103 'operator',
6714d8d2
SL
104 // CRM-20468
105 'content',
1107b319
SL
106 // CiviCampaign Goal Details
107 'goal_general',
edc4d8e7
MM
108 // https://lab.civicrm.org/dev/core/issues/1286
109 'header',
110 // https://lab.civicrm.org/dev/core/issues/1286
111 'footer',
23e56526
CW
112 // SavedSearch entity
113 'api_params',
07e07e87
CW
114 // SearchDisplay entity
115 'settings',
be2fb01f 116 ];
ece8de2d
CW
117 $custom = CRM_Core_DAO::executeQuery('SELECT id FROM civicrm_custom_field WHERE html_type = "RichTextEditor"');
118 while ($custom->fetch()) {
119 $this->skipFields[] = 'custom_' . $custom->id;
120 }
a9396478
TO
121 }
122 return $this->skipFields;
123 }
124
125 /**
dc195289 126 * going to filter the
a9396478
TO
127 * submitted values across XSS vulnerability.
128 *
129 * @param array|string $values
77855840
TO
130 * @param bool $castToString
131 * If TRUE, all scalars will be filtered (and therefore cast to strings).
a9396478
TO
132 * If FALSE, then non-string values will be preserved
133 */
134 public function encodeInput(&$values, $castToString = FALSE) {
135 if (is_array($values)) {
136 foreach ($values as &$value) {
137 $this->encodeInput($value, TRUE);
138 }
0db6c3e1
TO
139 }
140 elseif ($castToString || is_string($values)) {
d37a188f
TO
141 $values = $this->encodeValue($values);
142 }
143 }
144
145 public function encodeValue($value) {
146 return str_replace(['<', '>'], ['&lt;', '&gt;'], $value);
147 }
148
149 /**
150 * Perform in-place decode on strings (in a list of records).
151 *
152 * @param array $rows
153 * Ex in: $rows[0] = ['first_name' => 'A&W'].
154 * Ex out: $rows[0] = ['first_name' => 'A&amp;W'].
155 */
156 public function encodeRows(&$rows) {
157 foreach ($rows as $rid => $row) {
158 $this->encodeRow($rows[$rid]);
159 }
160 }
161
162 /**
163 * Perform in-place encode on strings (in a single record).
164 *
165 * @param array $row
166 * Ex in: ['first_name' => 'A&W'].
167 * Ex out: ['first_name' => 'A&amp;W'].
168 */
169 public function encodeRow(&$row) {
170 foreach ($row as $k => $v) {
171 if (is_string($v) && !$this->isSkippedField($k)) {
172 $row[$k] = $this->encodeValue($v);
173 }
a9396478
TO
174 }
175 }
176
5bc392e6 177 /**
ae5ffbb7 178 * @param array $values
5bc392e6
EM
179 * @param bool $castToString
180 */
a9396478
TO
181 public function decodeOutput(&$values, $castToString = FALSE) {
182 if (is_array($values)) {
183 foreach ($values as &$value) {
184 $this->decodeOutput($value, TRUE);
185 }
0db6c3e1
TO
186 }
187 elseif ($castToString || is_string($values)) {
d37a188f
TO
188 $values = $this->decodeValue($values);
189 }
190 }
191
192 public function decodeValue($value) {
193 return str_replace(['&lt;', '&gt;'], ['<', '>'], $value);
194 }
195
196 /**
197 * Perform in-place decode on strings (in a list of records).
198 *
199 * @param array $rows
200 * Ex in: $rows[0] = ['first_name' => 'A&amp;W'].
201 * Ex out: $rows[0] = ['first_name' => 'A&W'].
202 */
203 public function decodeRows(&$rows) {
204 foreach ($rows as $rid => $row) {
205 $this->decodeRow($rows[$rid]);
206 }
207 }
208
209 /**
210 * Perform in-place decode on strings (in a single record).
211 *
212 * @param array $row
213 * Ex in: ['first_name' => 'A&amp;W'].
214 * Ex out: ['first_name' => 'A&W'].
215 */
216 public function decodeRow(&$row) {
217 foreach ($row as $k => $v) {
218 if (is_string($v) && !$this->isSkippedField($k)) {
219 $row[$k] = $this->decodeValue($v);
220 }
a9396478
TO
221 }
222 }
96025800 223
a9396478 224}