checkbox.php
1 month ago
date.php
1 month ago
datetime.php
1 month ago
field.php
1 month ago
gallery.php
1 month ago
map.php
1 month ago
media-url.php
1 month ago
media.php
1 month ago
number.php
1 month ago
post.php
1 month ago
radio.php
1 month ago
repeater.php
1 month ago
select.php
1 month ago
switcher.php
1 month ago
text.php
1 month ago
time.php
1 month ago
toggle.php
1 month ago
user.php
1 month ago
repeater.php
252 lines
| 1 | <?php |
| 2 | |
| 3 | namespace Wpai\AddonAPI; |
| 4 | |
| 5 | if ( ! defined( 'ABSPATH' ) ) exit; |
| 6 | |
| 7 | class PMXI_Addon_Repeater_Field extends PMXI_Addon_Field { |
| 8 | |
| 9 | /* |
| 10 | * Parse the repeater and its subfields |
| 11 | */ |
| 12 | public function beforeImport($postId, $value, $data, $logger, $rawData) { |
| 13 | $mode = $value['mode'] ?? 'fixed'; |
| 14 | $ignore_blanks = (bool) ($value['ignore_blanks'] ?? false); |
| 15 | |
| 16 | $parseFunction = [ |
| 17 | 'variable-xml' => 'parseVariableXml', |
| 18 | 'variable-csv' => 'parseVariableCsv', |
| 19 | 'fixed' => 'parseFixed' |
| 20 | ][$mode]; |
| 21 | |
| 22 | $rows = $this->$parseFunction($postId, $value, $data, $logger, $rawData); |
| 23 | |
| 24 | return $ignore_blanks ? $this->filterOutBlankRows($rows) : $rows; |
| 25 | } |
| 26 | |
| 27 | /* |
| 28 | * Determine whether a row can be imported by checking if it has any non-empty values |
| 29 | * @param $row array |
| 30 | * @return bool |
| 31 | */ |
| 32 | public static function canImportRow($row) { |
| 33 | $filtered = array_filter($row, fn ($value) => !empty($value)); |
| 34 | return count($filtered) > 0; |
| 35 | } |
| 36 | |
| 37 | /* |
| 38 | * Get the row field value given a xpath. |
| 39 | * @param $xml string |
| 40 | * @param $rootNodeXPath string |
| 41 | * @param $xpath string|array |
| 42 | * @return mixed |
| 43 | */ |
| 44 | public function getRowFieldValue($xml, $rootNodeXPath, $xpath, $index) { |
| 45 | if (is_array($xpath)) { |
| 46 | $values = []; |
| 47 | foreach ($xpath as $key => $nestedXpath) { |
| 48 | // For some reason, the XML parsed doesn't like empty strings and will go into an infinite loop. |
| 49 | $values[$key] = $nestedXpath ? $this->getRowFieldValue($xml, $rootNodeXPath, $nestedXpath, $index) : $nestedXpath; |
| 50 | } |
| 51 | return $values; |
| 52 | } |
| 53 | |
| 54 | $file = false; |
| 55 | $parsed = \XmlImportParser::factory($xml, $rootNodeXPath . '[' . ($index + 1) . ']', $xpath, $file)->parse(); |
| 56 | wp_delete_file($file); |
| 57 | |
| 58 | return $parsed[0] ?? ''; |
| 59 | } |
| 60 | |
| 61 | /* |
| 62 | * Parse XML rows |
| 63 | * @param $xml string |
| 64 | * @param $cxpath string |
| 65 | * @param $template array |
| 66 | * @return array |
| 67 | */ |
| 68 | public function parseXmlRows($xml, $rootNodeXPath, $template) { |
| 69 | $file = false; |
| 70 | $parsed_rows = \XmlImportParser::factory($xml, $rootNodeXPath, "{.}", $file)->parse(); |
| 71 | wp_delete_file($file); |
| 72 | |
| 73 | $rows = []; |
| 74 | |
| 75 | for ($i = 0; $i < count($parsed_rows); $i++) { |
| 76 | $row = []; |
| 77 | |
| 78 | foreach ($template as $key => $xpath) { |
| 79 | $row[$key] = $this->getRowFieldValue($xml, $rootNodeXPath, $xpath, $i); |
| 80 | } |
| 81 | |
| 82 | $rows[$i] = $row; |
| 83 | } |
| 84 | |
| 85 | return $rows; |
| 86 | } |
| 87 | |
| 88 | /* |
| 89 | * Parse Variable XML |
| 90 | * @param $postId int |
| 91 | * @param $value array |
| 92 | * @param $data array |
| 93 | * @param $logger callable |
| 94 | * @param $rawData array |
| 95 | * @return array |
| 96 | */ |
| 97 | public function parseVariableXml($postId, $value, $data, $logger, $rawData) { |
| 98 | $template_rows = $rawData['rows'] ?? []; |
| 99 | $template = array_shift($template_rows); |
| 100 | $xml_for_each = ltrim(trim($rawData['foreach'] ?? '', '{}!'), '/'); |
| 101 | |
| 102 | // Empty foreach must not leave a trailing slash on the xpath. |
| 103 | $repeater_xpath = '[' . ($data['i'] + 1) . ']' . ($xml_for_each !== '' ? '/' . $xml_for_each : ''); |
| 104 | $xpath = $data['xpath_prefix'] . $data['import']->xpath . $repeater_xpath; |
| 105 | |
| 106 | $row_data = []; |
| 107 | $rows = $this->parseXmlRows($data['xml'], $xpath, $template); |
| 108 | |
| 109 | foreach ($rows as $index => $row) { |
| 110 | foreach ($this->subfields as $subfield) { |
| 111 | $field_instance = PMXI_Addon_Field::from($subfield, $this->view, $this); |
| 112 | $field_value = $row[$subfield['key']]; |
| 113 | $field_value_raw = $template[$subfield['key']]; |
| 114 | |
| 115 | $row_data[$index][$subfield['key']] = $field_instance->beforeImport( |
| 116 | $postId, |
| 117 | $field_value, |
| 118 | $data, |
| 119 | $logger, |
| 120 | $field_value_raw |
| 121 | ); |
| 122 | } |
| 123 | } |
| 124 | |
| 125 | return $row_data; |
| 126 | } |
| 127 | |
| 128 | /* |
| 129 | * Handle complex field types like Media, Gallery, and Posts. |
| 130 | */ |
| 131 | public function formatFieldValue($value, $delimiter, $repeater_path) { |
| 132 | if (isset($repeater_path)) { |
| 133 | $nested_path_values = explode($delimiter, $value[$repeater_path]); |
| 134 | $new_value = []; |
| 135 | |
| 136 | foreach ($nested_path_values as $nested_value) { |
| 137 | $new_value[] = array_merge( |
| 138 | $value, |
| 139 | [$repeater_path => $nested_value] |
| 140 | ); |
| 141 | } |
| 142 | |
| 143 | return $new_value; |
| 144 | } |
| 145 | |
| 146 | if (is_array($value)) { |
| 147 | return $value; |
| 148 | } |
| 149 | |
| 150 | return explode($delimiter, $value); |
| 151 | } |
| 152 | |
| 153 | /* |
| 154 | * Parse Variable CSV |
| 155 | * @param $postId int |
| 156 | * @param $value array |
| 157 | * @param $data array |
| 158 | * @param $logger callable |
| 159 | * @param $rawData array |
| 160 | * @return array |
| 161 | */ |
| 162 | public function parseVariableCsv($postId, $value, $data, $logger, $rawData) { |
| 163 | $rows = $value['rows'] ?? []; |
| 164 | $row = array_shift($rows); |
| 165 | $template_rows = $rawData['rows'] ?? []; |
| 166 | $template = array_shift($template_rows); |
| 167 | $delimiter = $value['separator'] ?? '|'; |
| 168 | $row_data = []; |
| 169 | |
| 170 | foreach ($this->subfields as $subfield) { |
| 171 | if (empty($row)) continue; |
| 172 | if (empty($row[$subfield['key']])) continue; |
| 173 | |
| 174 | $field_instance = PMXI_Addon_Field::from($subfield, $this->view, $this); |
| 175 | $field_value = $row[$subfield['key']]; |
| 176 | $field_value = $this->formatFieldValue( |
| 177 | $field_value, |
| 178 | $delimiter, |
| 179 | $field_instance::$repeater_path |
| 180 | ); |
| 181 | $field_value_raw = $template[$subfield['key']]; |
| 182 | |
| 183 | // Loop through each value and parse it as a field. |
| 184 | $current_row = array_map( |
| 185 | fn ($value) => |
| 186 | $field_instance->beforeImport( |
| 187 | $postId, |
| 188 | $value, |
| 189 | $data, |
| 190 | $logger, |
| 191 | $field_value_raw |
| 192 | ), |
| 193 | $field_value |
| 194 | ); |
| 195 | |
| 196 | |
| 197 | foreach ($current_row as $index => $v) { |
| 198 | $row_data[$index][$subfield['key']] = $v; |
| 199 | } |
| 200 | } |
| 201 | |
| 202 | return $row_data; |
| 203 | } |
| 204 | |
| 205 | /* |
| 206 | * Parse Fixed Mode |
| 207 | * @param $postId int |
| 208 | * @param $value array |
| 209 | * @param $data array |
| 210 | * @param $logger callable |
| 211 | * @param $rawData array |
| 212 | * @return array |
| 213 | */ |
| 214 | public function parseFixed($postId, $value, $data, $logger, $rawData) { |
| 215 | $template_rows = $rawData['rows'] ?? []; |
| 216 | $template = array_shift($template_rows); |
| 217 | $rows = $value['rows'] ?? []; |
| 218 | $row_data = []; |
| 219 | |
| 220 | foreach ($rows as $index => $row) { |
| 221 | foreach ($this->subfields as $subfield) { |
| 222 | if (empty($row)) continue; |
| 223 | |
| 224 | $field_instance = PMXI_Addon_Field::from($subfield, $this->view, $this); |
| 225 | $field_value = $row[$subfield['key']]; |
| 226 | $field_value_raw = $template[$subfield['key']]; |
| 227 | |
| 228 | $current_row = $field_instance->beforeImport( |
| 229 | $postId, |
| 230 | $field_value, |
| 231 | $data, |
| 232 | $logger, |
| 233 | $field_value_raw |
| 234 | ); |
| 235 | |
| 236 | $row_data[$index][$subfield['key']] = $current_row; |
| 237 | } |
| 238 | } |
| 239 | |
| 240 | return $row_data; |
| 241 | } |
| 242 | |
| 243 | public function filterOutBlankRows($rows) { |
| 244 | return array_values( |
| 245 | array_filter( |
| 246 | $rows, |
| 247 | [__CLASS__, 'canImportRow'] |
| 248 | ) |
| 249 | ); |
| 250 | } |
| 251 | } |
| 252 |