PluginProbe
TablePress – Tables in WordPress made easy / 3.0.4
TablePress – Tables in WordPress made easy v3.0.4
3.3.4 3.3.3 3.3.2 3.3.1 trunk 1.12 1.14 1.9.2 2.0.4 2.1.7 2.1.8 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3 2.3.1 2.3.2 2.4 2.4.1 2.4.2 2.4.3 2.4.4 All 44 releases
← All changes | classes/class-evaluate.php +29 -201 1.143.0.4 View file →
@@ -23,40 +23,33 @@
23 23 */
24 24 class TablePress_Evaluate {
25 25
26 26 /**
27 - * Instance of the EvalMath class.
27 + * Checks whether the requirements for the PHPSpreadsheet evaluate class are fulfilled or if the legacy evaluate class should be used.
28 28 *
29 - * @since 1.0.0
30 - * @var EvalMath
31 - */
32 - protected $evalmath;
33 -
34 - /**
35 - * Table data in which formulas shall be evaluated.
29 + * @since 2.0.0
36 30 *
37 - * @since 1.5.0
38 - * @var array
31 + * @return bool Whether the legacy evaluate class should be used.
39 32 */
40 - protected $table_data;
33 + protected function _should_use_legacy_evaluate_class(): bool {
34 + /**
35 + * Filters whether the Legacy Table Evaluate class shall be used.
36 + *
37 + * @since 2.0.0
38 + *
39 + * @param bool $use_legacy_class Whether to use the legacy table evaluate class. Default false.
40 + */
41 + if ( apply_filters( 'tablepress_use_legacy_table_evaluate_class', false ) ) {
42 + return true;
43 + }
41 44
42 - /**
43 - * Storage for cell ranges that have been replaced in formulas.
44 - *
45 - * @since 1.0.0
46 - * @var array
47 - */
48 - protected $known_ranges = array();
45 + // Use the legacy evaluate class, if the requirements for PHPSpreadsheet Calculations are not fulfilled.
46 + $phpspreadsheet_requirements_fulfilled = extension_loaded( 'mbstring' );
47 + if ( ! $phpspreadsheet_requirements_fulfilled ) {
48 + return true;
49 + }
49 50
50 - /**
51 - * Initialize the Formula Evaluation class, include the EvalMath class.
52 - *
53 - * @since 1.0.0
54 - */
55 - public function __construct() {
56 - $this->evalmath = TablePress::load_class( 'EvalMath', 'evalmath.class.php', 'libraries' );
57 - // Don't raise PHP warnings.
58 - $this->evalmath->suppress_errors = true;
51 + return false;
59 52 }
60 53
61 54 /**
62 55 * Evaluate formulas in the passed table.
@@ -62,185 +55,20 @@
62 55 * Evaluate formulas in the passed table.
63 56 *
64 57 * @since 1.0.0
65 58 *
66 - * @param array $table_data Table data in which formulas shall be evaluated.
67 - * @param string $table_id ID of the passed table.
68 - * @return array Table data with evaluated formulas.
59 + * @param array<int, array<int, string>> $table_data Table data in which formulas shall be evaluated.
60 + * @param string $table_id ID of the passed table.
61 + * @return array<int, array<int, string>> Table data with evaluated formulas.
69 62 */
70 - public function evaluate_table_data( array $table_data, $table_id ) {
71 - $this->table_data = $table_data;
72 - $num_rows = count( $this->table_data );
73 - $num_columns = count( $this->table_data[0] );
74 -
75 - // Make fixed table data available as variables in formulas.
76 - $this->evalmath->variables['table_id'] = $table_id;
77 - $this->evalmath->variables['num_rows'] = $num_rows;
78 - $this->evalmath->variables['num_columns'] = $num_columns;
79 -
80 - // Use two for-loops instead of foreach here to be sure to always work on the "live" table data and not some in-memory copy.
81 - for ( $row_idx = 0; $row_idx < $num_rows; $row_idx++ ) {
82 - for ( $col_idx = 0; $col_idx < $num_columns; $col_idx++ ) {
83 - $this->table_data[ $row_idx ][ $col_idx ] = $this->_evaluate_cell( $this->table_data[ $row_idx ][ $col_idx ], $row_idx, $col_idx );
84 - }
85 - }
86 -
87 - return $this->table_data;
88 - }
89 -
90 - /**
91 - * Parse and evaluate the content of a cell.
92 - *
93 - * @since 1.0.0
94 - *
95 - * @param string $content Content of a cell.
96 - * @param int $row_idx Row index of the cell.
97 - * @param int $col_idx Column index of the cell.
98 - * @param array $parents Optional. List of cells that depend on this cell (to prevent circle references).
99 - * @return string Result of the parsing/evaluation.
100 - */
101 - protected function _evaluate_cell( $content, $row_idx, $col_idx, array $parents = array() ) {
102 - if ( '' === $content || '=' === $content || '=' !== $content[0] ) {
103 - return $content;
104 - }
105 -
106 - // Cut off the leading =.
107 - $content = substr( $content, 1 );
108 -
109 - // Support putting formulas in strings, like =Total: {A3+A4}.
110 - $expressions = array();
111 - if ( preg_match_all( '#{(.+?)}#', $content, $expressions, PREG_SET_ORDER ) ) {
112 - $formula_in_string = true;
63 + public function evaluate_table_data( array $table_data, string $table_id ): array {
64 + // Choose the Table Evaluate library based on the PHP version and the filter hook value.
65 + if ( $this->_should_use_legacy_evaluate_class() ) {
66 + $evaluate_class = TablePress::load_class( 'TablePress_Evaluate_Legacy', 'class-evaluate-legacy.php', 'classes' );
113 67 } else {
114 - $formula_in_string = false;
115 - // Fill array so that it has the same structure as if it came from preg_match_all().
116 - $expressions[] = array( $content, $content );
68 + $evaluate_class = TablePress::load_class( 'TablePress_Evaluate_PHPSpreadsheet', 'class-evaluate-phpspreadsheet.php', 'classes' );
117 69 }
118 70
119 - foreach ( $expressions as $expression ) {
120 - $orig_expression = $expression[0];
121 - $expression = $expression[1];
122 -
123 - $replaced_references = array();
124 - $replaced_ranges = array();
125 -
126 - // Remove all whitespace characters.
127 - $expression = str_replace( array( "\n", "\r", "\t", ' ' ), '', $expression );
128 -
129 - // Expand cell ranges (like A3:A6) to a list of single cells (like A3,A4,A5,A6).
130 - if ( preg_match_all( '#([A-Z]+)([0-9]+):([A-Z]+)([0-9]+)#', $expression, $referenced_cell_ranges, PREG_SET_ORDER ) ) {
131 - foreach ( $referenced_cell_ranges as $cell_range ) {
132 - if ( in_array( $cell_range[0], $replaced_ranges, true ) ) {
133 - continue;
134 - }
135 -
136 - $replaced_ranges[] = $cell_range[0];
137 -
138 - if ( isset( $this->known_ranges[ $cell_range[0] ] ) ) {
139 - $expression = preg_replace( '#(?<![A-Z])' . preg_quote( $cell_range[0], '#' ) . '(?![0-9])#', $this->known_ranges[ $cell_range[0] ], $expression );
140 - continue;
141 - }
142 -
143 - // No -1 necessary for this transformation, as we don't actually access the table.
144 - $first_col = TablePress::letter_to_number( $cell_range[1] );
145 - $first_row = $cell_range[2];
146 - $last_col = TablePress::letter_to_number( $cell_range[3] );
147 - $last_row = $cell_range[4];
148 -
149 - $col_start = min( $first_col, $last_col );
150 - $col_end = max( $first_col, $last_col ) + 1; // +1 for loop below
151 - $row_start = min( $first_row, $last_row );
152 - $row_end = max( $first_row, $last_row ) + 1; // +1 for loop below
153 -
154 - $cell_list = array();
155 - for ( $col = $col_start; $col < $col_end; $col++ ) {
156 - for ( $row = $row_start; $row < $row_end; $row++ ) {
157 - $column = TablePress::number_to_letter( $col );
158 - $cell_list[] = "{$column}{$row}";
159 - }
160 - }
161 - $cell_list = implode( ',', $cell_list );
162 -
163 - $expression = preg_replace( '#(?<![A-Z])' . preg_quote( $cell_range[0], '#' ) . '(?![0-9])#', $cell_list, $expression );
164 - $this->known_ranges[ $cell_range[0] ] = $cell_list;
165 - }
166 - }
167 -
168 - // Parse and evaluate single cell references (like A3 or XY312), while prohibiting circle references.
169 - if ( preg_match_all( '#([A-Z]+)([0-9]+)(?![0-9A-Z\(])#', $expression, $referenced_cells, PREG_SET_ORDER ) ) {
170 - foreach ( $referenced_cells as $cell_reference ) {
171 - if ( in_array( $cell_reference[0], $parents, true ) ) {
172 - return '!ERROR! Circle Reference';
173 - }
174 -
175 - if ( in_array( $cell_reference[0], $replaced_references, true ) ) {
176 - continue;
177 - }
178 -
179 - $replaced_references[] = $cell_reference[0];
180 -
181 - $ref_col = TablePress::letter_to_number( $cell_reference[1] ) - 1;
182 - $ref_row = $cell_reference[2] - 1;
183 -
184 - if ( ! isset( $this->table_data[ $ref_row ][ $ref_col ] ) ) {
185 - return "!ERROR! Cell {$cell_reference[0]} does not exist";
186 - }
187 -
188 - $ref_parents = $parents;
189 - $ref_parents[] = $cell_reference[0];
190 -
191 - $result = $this->_evaluate_cell( $this->table_data[ $ref_row ][ $ref_col ], $ref_row, $ref_col, $ref_parents );
192 - $this->table_data[ $ref_row ][ $ref_col ] = $result;
193 - // Bail if there was an error already.
194 - if ( false !== strpos( $result, '!ERROR!' ) ) {
195 - return $result;
196 - }
197 - // Remove all whitespace characters.
198 - $result = str_replace( array( "\n", "\r", "\t", ' ' ), '', $result );
199 - // Treat empty cells as 0.
200 - if ( '' === $result ) {
201 - $result = 0;
202 - }
203 - // Bail if the cell does not result in a number (meaning it was a number or expression before being evaluated).
204 - if ( ! is_numeric( $result ) ) {
205 - return "!ERROR! {$cell_reference[0]} does not contain a number or expression";
206 - }
207 -
208 - $expression = preg_replace( '#(?<![A-Z])' . $cell_reference[0] . '(?![0-9])#', $result, $expression );
209 - }
210 - }
211 -
212 - $result = $this->_evaluate_math_expression( $expression, $row_idx, $col_idx );
213 - // Support putting formulas in strings, like =Total: {A3+A4}.
214 - if ( $formula_in_string ) {
215 - $content = str_replace( $orig_expression, $result, $content );
216 - } else {
217 - $content = $result;
218 - }
219 - }
220 -
221 - return $content;
222 - }
223 -
224 - /**
225 - * Evaluate a math expression.
226 - *
227 - * @since 1.0.0
228 - *
229 - * @param string $expression without leading = sign.
230 - * @return string Result of the evaluation.
231 - */
232 - protected function _evaluate_math_expression( $expression, $row_idx, $col_idx ) {
233 - // Make current cell's name and row and column number available as variables in formulas.
234 - $this->evalmath->variables['row'] = $row_idx + 1;
235 - $this->evalmath->variables['column'] = $col_idx + 1;
236 - $this->evalmath->variables['cell'] = TablePress::number_to_letter( $this->evalmath->variables['column'] ) . $this->evalmath->variables['row'];
237 - // Straight up evaluation, without parsing of variable or function assignments (which is why we only need one instance of the object).
238 - $result = $this->evalmath->evaluate( $expression );
239 - if ( false === $result ) {
240 - return '!ERROR! ' . $this->evalmath->last_error;
241 - } else {
242 - return (string) $result;
243 - }
71 + return $evaluate_class->evaluate_table_data( $table_data, $table_id );
244 72 }
245 73
246 74 } // class TablePress_Evaluate