repository = $repository; $this->references = $references ? $references : new NestedReferenceParser(); $this->builtins = $builtins ? $builtins : new BuiltinVariables(); } public function resolve( $key, $context = array() ) { $key = sanitize_key( $key ); if ( '' === $key ) { return null; } $context = is_array( $context ) ? $context : array(); $locale_explicit = array_key_exists( '_glovario_locale_explicit', $context ) ? (bool) $context['_glovario_locale_explicit'] : array_key_exists( 'locale', $context ); $context = wp_parse_args( $context, array( 'post_id' => get_the_ID() ? get_the_ID() : 0, 'post_type' => get_post_type() ? get_post_type() : '', 'locale' => determine_locale(), 'user_id' => get_current_user_id(), 'timestamp' => time(), 'source' => 'api', ) ); $context['_glovario_locale_explicit'] = $locale_explicit; $is_root = empty( $this->stack ); if ( $is_root ) { /** * Filters the maximum number of variables in one nested resolution chain. * * @param int $maximum Maximum depth, including the root variable. * @param string $key Root variable key. * @param array $context Resolution context. */ $this->maximum_depth = min( 50, max( 1, absint( apply_filters( 'glovario_nested_max_depth', 10, $key, $context ) ) ) ); /** * Filters the total nested-reference budget for one root resolution. * * @param int $maximum Maximum references expanded across the complete tree. * @param string $key Root variable key. * @param array $context Resolution context. */ $this->remaining_references = min( 1000, max( 1, absint( apply_filters( 'glovario_nested_max_references', 100, $key, $context ) ) ) ); } if ( in_array( $key, $this->stack, true ) ) { return null; } $this->stack[] = $key; try { do_action( 'glovario_before_resolve', $key, $context ); $variable = $this->builtins->find_by_key( $key, $context ); if ( ! $variable ) { $variable = $this->repository->find_by_key( $key ); } if ( ! $variable ) { /** * Supplies a variable that is not stored on this site, such as a * multisite network variable. Site variables always take precedence. * * @param array|null $variable Variable row, or null when unknown. * @param string $key Requested key. * @param array $context Resolution context. */ $variable = apply_filters( 'glovario_missing_variable', null, $key, $context ); } if ( ! is_array( $variable ) ) { return null; } $value = apply_filters( 'glovario_resolved_value', $variable['value'], $variable, $context ); $value = $this->expand_nested( $value, $context ); return array( 'value' => $value, 'variable' => $variable, 'context' => $context, ); } finally { array_pop( $this->stack ); if ( $is_root ) { $this->remaining_references = 0; } } } private function expand_nested( $value, $context ) { return $this->references->replace( $value, function ( $key, $fallback, $date_format ) use ( $context ) { if ( '' === $key ) { return $fallback; } if ( in_array( $key, $this->stack, true ) ) { $this->blocked_reference( $key, 'cycle', $context ); return $fallback; } if ( count( $this->stack ) >= $this->maximum_depth ) { $this->blocked_reference( $key, 'depth', $context ); return $fallback; } if ( 0 >= $this->remaining_references ) { $this->blocked_reference( $key, 'budget', $context ); return $fallback; } --$this->remaining_references; $nested_context = $context; if ( BuiltinVariables::CURRENT_DATE_KEY === $key ) { $nested_context['date_format'] = $date_format; } $resolved = $this->resolve( $key, $nested_context ); return $resolved ? (string) $resolved['value'] : $fallback; } ); } private function blocked_reference( $key, $reason, $context ) { $parent = end( $this->stack ); /** * Fires when a nested reference is safely skipped. * * @param string $key Referenced variable key. * @param string $parent Variable containing the reference. * @param string $reason One of cycle, depth, or budget. * @param array $context Resolution context. */ do_action( 'glovario_nested_reference_blocked', $key, $parent, $reason, $context ); } }