Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
31.82% covered (danger)
31.82%
21 / 66
50.00% covered (danger)
50.00%
3 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
Contact_Form_Shortcode
31.82% covered (danger)
31.82%
21 / 66
50.00% covered (danger)
50.00%
3 / 6
160.78
0.00% covered (danger)
0.00%
0 / 1
 __construct
50.00% covered (danger)
50.00%
4 / 8
0.00% covered (danger)
0.00%
0 / 1
4.12
 parse_content
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 get_attribute
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 esc_attr
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
6
 unesc_attr
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
2
 __toString
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
132
1<?php
2/**
3 * Contact_Form_Shortcode class.
4 *
5 * @package automattic/jetpack-forms
6 */
7
8namespace Automattic\Jetpack\Forms\ContactForm;
9
10/**
11 * Generic shortcode class.
12 * Does nothing other than store structured data and output the shortcode as a string
13 *
14 * Not very general - specific to Grunion.
15 *
16 * // phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound
17 */
18class Contact_Form_Shortcode {
19    /**
20     * The name of the shortcode: [$shortcode_name /].
21     *
22     * @var string
23     */
24    public $shortcode_name;
25
26    /**
27     * Key => value pairs for the shortcode's attributes: [$shortcode_name key="value" ... /]
28     *
29     * @var array
30     */
31    public $attributes;
32
33    /**
34     * Key => value pair for attribute defaults.
35     *
36     * @var array
37     */
38    public $defaults = array();
39
40    /**
41     * The inner content of otherwise: [$shortcode_name]$content[/$shortcode_name]. Null for selfclosing shortcodes.
42     *
43     * @var null|string
44     */
45    public $content;
46
47    /**
48     * Associative array of inner "child" shortcodes equivalent to the $content: [$shortcode_name][child 1/][child 2/][/$shortcode_name]
49     *
50     * @var array
51     */
52    public $fields;
53
54    /**
55     * The HTML of the parsed inner "child" shortcodes".  Null for selfclosing shortcodes.
56     *
57     * @var null|string
58     */
59    public $body;
60
61    /**
62     * Constructor function.
63     *
64     * @param array       $attributes An associative array of shortcode attributes.  @see shortcode_atts().
65     * @param null|string $content Null for selfclosing shortcodes.  The inner content otherwise.
66     */
67    public function __construct( $attributes, $content = null ) {
68        $this->attributes = $this->unesc_attr( $attributes );
69        if ( is_array( $content ) ) {
70            $string_content = '';
71            foreach ( $content as $field ) {
72                $string_content .= (string) $field;
73            }
74
75            $this->content = $string_content;
76        } else {
77            $this->content = $content;
78        }
79
80        $this->parse_content( $this->content );
81    }
82
83    /**
84     * Processes the shortcode's inner content for "child" shortcodes.
85     *
86     * @param string $content The shortcode's inner content: [shortcode]$content[/shortcode].
87     */
88    public function parse_content( $content ) {
89        if ( $content === null ) {
90            $this->body = null;
91        } else {
92            $this->body = do_shortcode( $content );
93        }
94    }
95
96    /**
97     * Returns the value of the requested attribute.
98     *
99     * @param string $key The attribute to retrieve.
100     *
101     * @return mixed
102     */
103    public function get_attribute( $key ) {
104        return $this->attributes[ $key ] ?? null;
105    }
106
107    /**
108     * Escape attributes.
109     *
110     * @param array $value - the value we're escaping.
111     *
112     * @return array
113     */
114    public function esc_attr( $value ) {
115        if ( is_array( $value ) ) {
116            return array_map( array( $this, 'esc_attr' ), $value );
117        }
118
119        $value = Contact_Form_Plugin::strip_tags( $value );
120        $value = _wp_specialchars( $value, ENT_QUOTES, false, true );
121
122        // Shortcode attributes can't contain "]"
123        $value = str_replace( ']', '', $value );
124        $value = str_replace( ',', '&#x002c;', $value ); // store commas encoded
125        $value = strtr(
126            $value,
127            array(
128                '%' => '%25',
129                '&' => '%26',
130            )
131        );
132
133        // shortcode_parse_atts() does stripcslashes() so we have to do it here.
134        $value = addslashes( $value );
135        return $value;
136    }
137
138    /**
139     * Unescape attributes.
140     *
141     * @param array $value - the value we're escaping.
142     *
143     * @return array
144     */
145    public function unesc_attr( $value ) {
146        if ( is_array( $value ) ) {
147            return array_map( array( $this, 'unesc_attr' ), $value );
148        }
149
150        // For back-compat with old Grunion encoding
151        // Also, unencode commas
152        $value = strtr(
153            (string) $value,
154            array(
155                '%26' => '&',
156                '%25' => '%',
157            )
158        );
159        $value = preg_replace( array( '/&#x0*22;/i', '/&#x0*27;/i', '/&#x0*26;/i', '/&#x0*2c;/i' ), array( '"', "'", '&', ',' ), $value );
160        $value = htmlspecialchars_decode( $value, ENT_QUOTES );
161        $value = Contact_Form_Plugin::strip_tags( $value );
162
163        return $value;
164    }
165
166    /**
167     * Generates the shortcode
168     */
169    public function __toString() {
170        $r = "[{$this->shortcode_name} ";
171        foreach ( $this->attributes as $key => $value ) {
172            if ( ! $value ) {
173                continue;
174            }
175
176            if ( isset( $this->defaults[ $key ] ) && $this->defaults[ $key ] === $value ) {
177                continue;
178            }
179
180            if ( 'id' === $key ) {
181                continue;
182            }
183
184            $value = $this->esc_attr( $value );
185
186            if ( is_array( $value ) ) {
187                $value = implode( ',', $value );
188            }
189
190            if ( ! str_contains( $value, "'" ) ) {
191                $value = "'$value'";
192            } elseif ( ! str_contains( $value, '"' ) ) {
193                $value = '"' . $value . '"';
194            } else {
195                // Shortcodes can't contain both '"' and "'".  Strip one.
196                $value = str_replace( "'", '', $value );
197                $value = "'$value'";
198            }
199
200            $r .= "{$key}={$value} ";
201        }
202
203        $r = rtrim( $r );
204
205        if ( $this->fields ) {
206            $r .= ']';
207
208            foreach ( $this->fields as $field ) {
209                $r .= (string) $field;
210            }
211
212            $r .= "[/{$this->shortcode_name}]";
213        } else {
214            $r .= '/]';
215        }
216
217        return $r;
218    }
219}