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