Current Path : /home/church/www/wp-content/plugins/ninja-forms/includes/Handlers/ |
Current File : /home/church/www/wp-content/plugins/ninja-forms/includes/Handlers/FieldsetRepeater.php |
<?php /** * Handles data of a FieldsetRepeater * * Fieldset repeater field data are stored as part of the single fieldset * repeater field. This includes both settings and submission data. Since these * data are not managed by NF standard data handling, this class manages it. * * Requests for a field can be made by either an (int) field id or a * (string) field reference, which prior to fieldset repeaters had been * for the field key only. * * Fieldset fields are stored as {fieldsetRepeaterFieldId}{fieldsetDelimiter}{fieldsetFieldId}{submissionIndexDelimiter}{submissionIndex} * * FieldSettings are passed into this class so that this class is not dependent * on NF core */ class NF_Handlers_FieldsetRepeater { /** * Delimiter separating fieldId from fieldsetFieldId * * Fieldset fields are individual fields within a fieldset. * * @var string */ protected $fieldsetDelimiter = '.'; /** * Delimiter that uniquely identifies multiple fieldset repeater submissions * * Fieldset Repeaters can have multiple values submitted on any given * submission. Each repeated value for a field in the fieldset is * delimited in the submission data with an incremented index value * @var string */ protected $submissionIndexDelimiter = '_'; /** * Returns labels for the fieldset's fields keyed on id of each fieldset field * * @param string $fieldId ID of the Fieldset Repeater field * @param array $fieldSettings Provided by (obj)$field->get_settings() * @param bool $useAdminLabels * @return array */ public function getFieldsetLabels($fieldId, $fieldSettings, $useAdminLabels = false) { // Default is fieldset's label if ($useAdminLabels && !empty($fieldSettings['admin_label'])) { $label = $fieldSettings['admin_label']; } else { $label = $fieldSettings['label']; } // If this isn't the expected 'repeater' type, // or if fields definition isn't set, return default if ( 'repeater' !== $fieldSettings['type'] || !isset($fieldSettings['fields']) || !is_array($fieldSettings['fields']) ) { return array($fieldId => $label); } $labels = array(); foreach ($fieldSettings['fields'] as $field) { $id = $field['id']; if ($useAdminLabels && '' !== $field['admin_label']) { $label = $field['admin_label']; } else { $label = $field['label']; } $labels[$id] = $label; } return $labels; } /** * Returns fieldsetField types keyed on fieldsetField ids * @param string $fieldId ID of the Fieldset Repeater field * @param array $fieldSettings Provided by (obj)$field->get_settings() * @return array */ public function getFieldsetTypes($fieldId, $fieldSettings) { $fieldsetFieldTypes = []; // If this isn't the expected 'repeater' type, // or if fields definition isn't set, return default if ( 'repeater' !== $fieldSettings['type'] || !isset($fieldSettings['fields']) || !is_array($fieldSettings['fields']) ) { return $fieldsetFieldTypes; } foreach ($fieldSettings['fields'] as $field) { $idArray = $this->parseFieldsetFieldReference($field['id']); $id = $fieldId . $this->fieldsetDelimiter . $idArray['fieldsetFieldId']; $type = $field['type']; $fieldsetFieldTypes[$id] = $type; } return $fieldsetFieldTypes; } /** * Given a field reference (ID or Key), return boolean for 'is repeater field' * * Determines if the given field reference is a fieldset repeater construct. * This is NOT the parent field; this is a request for a child field within * the fieldset repeater. The field settings and values for such a field * are stored differently than a standard field, so we need to know how * to make requests for its settings/data. * * For disambiguation, a fieldset repeater field * request for a specific field within the fieldset is in the form of: * {fieldsetFieldId}{fieldsetDelimiter}{submissionIndexDelimiter} * * * @param int|string $fieldReference ID or key for the field * @return bool */ public function isRepeaterFieldByFieldReference($fieldReference) { $return = false; $exploded = explode($this->fieldsetDelimiter, $fieldReference); if (isset($exploded[1])) { $return = true; } return $return; } /** * Determine if data matches fieldset repeater construct * * When given only a submission value without any meta data, check the * construct of the value to asssert with some level of confidence that the * value is from a fieldset repeater. * * Logic: * - is submission empty? then NO, we don't assert is is fieldset repeater * data * - can the array key be parsed as a fieldset repeater key? If not, then * NO... * - is each value an array with 'id' and 'value' keys, and the `id` * matches the id of its parent? If not, then NO... * * If all the above conditions are met for every entry in the submission, * we assert that the submission value is that of a fieldset repeater. * * * @param array $submission * @return boolean */ public function isFieldsetData(array $submission) { $return = true; // If not array containing data, not fieldset repeater if (empty($submission)) { $return = false; } foreach($submission as $key=>$submissionValueArray){ $submissionReference = $this->parseFieldsetFieldReference($key); if(-1===$submissionReference){ $return = false; } if(!isset($submissionValueArray['id']) || $key!==$submissionValueArray['id'] || !isset($submissionValueArray['value'])){ $return = false; } } return $return; } /** * Parse field id, fieldset id, and submission index * * Returns array of fieldId, fieldsetFieldId, submissionId * If failing, fieldsetFieldId = -1 * * @param string $reference * @return array */ public function parseSubmissionReference( $reference) { $fieldset= $this->parseFieldsetFieldReference($reference); $fieldId=$fieldset['fieldId']; $submissionIndex = $this->parseSubmissionIndex($fieldset['fieldsetFieldId']); $fieldsetFieldId=$submissionIndex['fieldsetFieldId']; $submissionId=$submissionIndex['submissionIndex']; $return = array( 'fieldId' => $fieldId, 'fieldsetFieldId' => $fieldsetFieldId, 'submissionId'=>$submissionId ); return $return; } /** * Given field reference, return field Id and fieldset field id * * Fieldset field is a field within the fieldset repeater. The child's field * settings and its submission data are not stored individually in the * field or submission tables, but rather as nested data inside the * parent's keyed location. * * Caller should ensure field is fieldset type before calling. * * @param string $fieldReference * * @return array Keys: 'fieldId', 'fieldsetFieldId' */ public function parseFieldsetFieldReference($fieldReference) { $return = array( 'fieldId' => 0, 'fieldsetFieldId' => 0 ); if ($this->isRepeaterFieldByFieldReference($fieldReference)) { $exploded = explode($this->fieldsetDelimiter, $fieldReference); $return = array( 'fieldId' => $exploded[0], 'fieldsetFieldId' => $exploded[1] ); } return $return; } /** * Parses fieldsetFieldId and submissionIndex keys * * Given string of expect fieldsetField and submissionIndex as a key under * which submission data is stored, returns the fieldsetFieldId and * submissionIndex id * * If cannot be parsed as expected, default values of -1 are returned to * notify of failure * * @param string $submissionIndex * @return array */ public function parseSubmissionIndex($submissionIndex) { $return = array( 'fieldsetFieldId' => -1, 'submissionIndex' => 0 // if no index present, set as 0 for an un-repeated fieldset ); $exploded = explode($this->submissionIndexDelimiter, $submissionIndex); $fieldsetFieldId = $exploded[0]; if (isset($exploded[1])) { $submissionIndex=$exploded[1]; } $return = array( 'fieldsetFieldId' => $fieldsetFieldId, 'submissionIndex' => $submissionIndex ); return $return; } /** * Returns field type of a field within a fieldset, given the field reference * * Field reference is the id of the field WITHIN the fieldset. The fieldset * has a numerical field id under which all settings and submission values * are stored for any field within the fieldset. Access to that setting * and submission data are not handled by the standard core functions and * are done through this class. * * @param string $fieldsetFieldId Fieldset Field reference * @param array $fieldSettings Field settings (from (obj)$field->get_settings()) * @return string */ public function getFieldtype($fieldsetFieldId, $fieldSettings) { $return = 'unknown'; if (!isset($fieldSettings['fields'])) { return $return; } // Ids for fieldset fields $idLookup = array_column($fieldSettings['fields'], 'id'); if (in_array($fieldsetFieldId, $idLookup)) { $indexLookup = array_flip($idLookup); $return = $fieldSettings['fields'][$indexLookup[$fieldsetFieldId]]['type']; } return $return; } /** * Extract all repeater submission values for a given fieldset field * * Fieldset data is all stored within the main fieldset field. To prevent * every caller from having to know the internal structure of the stored * data, this method enables callers to provide the requested Fieldset * Field's reference id with the full submission data and receive in * return all the submitted values for that given field. * * @param string $fieldsetFieldId Fieldset Field reference * @param array $fieldSubmissionValue Submission data for entire fieldset */ public function extractSubmissionsByFieldsetField($fieldsetFieldId, $fieldSubmissionValue) { $return = array(); foreach ($fieldSubmissionValue as $submissionId => $submissionValueArray) { $exploded = explode($this->submissionIndexDelimiter, $submissionId); if ($fieldsetFieldId === $exploded[0]) { $return[] = $submissionValueArray; } } return $return; } /** * Extract fieldset repeater submissions by submission index and fieldset * field * * Unknown values can be passed as empty string or arrays; the method will * fill in what it can and set default values for those it can't * * @todo Refactor this method after unit testing is in place. It is being * used to share a common structure for output but refactoring should wait * until unit testing can ensure the data structure of responses don't * change during refactor. * * @param string $fieldId * @param array $fieldSubmissionValue Submission data array for entire field * @param array $fieldSettings Field settings (from * (obj)$field->get_settings()) * @return array Array of submission values * * {submissionIndex}=> {fieldsetFieldId}=>['value'=>{submitted value} * 'type'=> {field type}, 'label'=> {label} * ] */ public function extractSubmissions($fieldId, $fieldSubmissionValue, $fieldSettings, $useAdminLabels = false) { $return = []; if (!is_array($fieldSubmissionValue)) { return $return; } if(''!==$fieldId and []!== $fieldSettings){ $fieldsetLabelLookup = $this->getFieldsetLabels($fieldId, $fieldSettings); $fieldsetTypeLookup = $this->getFieldsetTypes($fieldId,$fieldSettings); }else{ $fieldsetLabelLookup = null; $fieldsetTypeLookup = null; } // $completeFieldsetID is in format {fieldsetRepeaterFieldId}{fieldsetDelimiter}{fieldsetFieldId}{submissionIndexDelimiter}{submissionIndex} foreach ($fieldSubmissionValue as $completeFieldsetId => $incomingValueArray) { // value is expected to be keyed inside incoming value array if (isset($incomingValueArray['value'])) { $value = $incomingValueArray['value']; } else { $value = $incomingValueArray; } // attempt parsing of fielsetField; if any fail, exit as data is corrupt $fieldsetWithSubmissionIndex = $this->parseFieldsetFieldReference($completeFieldsetId); if (0 == $fieldsetWithSubmissionIndex['fieldsetFieldId']) { return $return; } $parsedSubmissionIds = $this->parseSubmissionIndex($fieldsetWithSubmissionIndex['fieldsetFieldId']); if (-1 === $parsedSubmissionIds['submissionIndex']) { return $return; } $fieldsetFieldId = $parsedSubmissionIds['fieldsetFieldId']; $submissionIndex = $parsedSubmissionIds['submissionIndex']; $idKey = $fieldId . $this->fieldsetDelimiter . $fieldsetFieldId; if(is_null($fieldsetTypeLookup)){ $fieldsetFieldType=''; }else{ $fieldsetFieldType = $fieldsetTypeLookup[$idKey]; } if(is_null($fieldsetLabelLookup)){ $fieldsetFieldLabel=''; }else{ $fieldsetFieldLabel = $fieldsetLabelLookup[$idKey]; } $array = []; $array['value'] = $value; $array['type'] = $fieldsetFieldType; $array['label'] = $fieldsetFieldLabel; $return[$submissionIndex][$fieldsetFieldId] = $array; } return $return; } }