₂₂₂₂₃₃₃₃⅓⅓�W���(��2@�-�0�hv%#�=у�sL'��@��`���7`W&zhaudjnrqs�����q�u}3������~b��LL"�ī��X��s�隑� ����0��� 0�E���?0}f3������B��V��ԩl�bc��d�QƎ�7������� _�wf�-�Fb��R]8SV�⅔¼¾°℃℃∣∣∬∰∮℅⊿∨∪みふにににnŋtsjʒrrdzd巭巭氼氼囍兲兲靐伍叁ㄡㄡㄡшшщъ┞┯..;~& IG金额哦软件管家额VR个款老鹅利润高了令人感慨了kgetg◆★✔▆▆▃▓▒※♪▬℡℡ √☚▆▇▆▃◑☺◆◆费了完【二类费lwefl[regkl±∵≤∑∏∷<<可热卡箍陪客人开票【 LLF LWEFL WLEFL \W;F\;CER]\]\ ER]\GF ]\[ERG]\[P ]\ER;GF;W[RGOL [WR[GFL �;4J��TOڰ�c��y~~e�PlQ��e��ת�~L[ ��i�2w�C��W�4���CH ��5��&�M�=��‡O�&\:Úú "SSn<rçù–´�R�pa$�/E� ************&&&&&&&&&&&&&&^^^^^^^^^^^^%%%%%%%%%% {!��N���! O��`F..>>>>>>>>>>> 0˜S�4 &m�[��� @9��~i8� ����d!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! @@@@@@@@&&&&&&&&&& kgfpok ergjf ?????????????? ???????????? Hooks.php000064400000005730152505214730006351 0ustar000 is executed later * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $hook argument is not a string. * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $callback argument is not callable. * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $priority argument is not an integer. */ public function register($hook, $callback, $priority = 0) { if (is_string($hook) === false) { throw InvalidArgument::create(1, '$hook', 'string', gettype($hook)); } if (is_callable($callback) === false) { throw InvalidArgument::create(2, '$callback', 'callable', gettype($callback)); } if (InputValidator::is_numeric_array_key($priority) === false) { throw InvalidArgument::create(3, '$priority', 'integer', gettype($priority)); } if (!isset($this->hooks[$hook])) { $this->hooks[$hook] = [ $priority => [], ]; } elseif (!isset($this->hooks[$hook][$priority])) { $this->hooks[$hook][$priority] = []; } $this->hooks[$hook][$priority][] = $callback; } /** * Dispatch a message * * @param string $hook Hook name * @param array $parameters Parameters to pass to callbacks * @return boolean Successfulness * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $hook argument is not a string. * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $parameters argument is not an array. */ public function dispatch($hook, $parameters = []) { if (is_string($hook) === false) { throw InvalidArgument::create(1, '$hook', 'string', gettype($hook)); } // Check strictly against array, as Array* objects don't work in combination with `call_user_func_array()`. if (is_array($parameters) === false) { throw InvalidArgument::create(2, '$parameters', 'array', gettype($parameters)); } if (empty($this->hooks[$hook])) { return false; } if (!empty($parameters)) { // Strip potential keys from the array to prevent them being interpreted as parameter names in PHP 8.0. $parameters = array_values($parameters); } ksort($this->hooks[$hook]); foreach ($this->hooks[$hook] as $priority => $hooked) { foreach ($hooked as $callback) { $callback(...$parameters); } } return true; } public function __wakeup() { throw new \LogicException( __CLASS__ . ' should never be unserialized' ); } } Iri.php000064400000072036152505214730006014 0ustar00 array( 'port' => Port::ACAP, ), 'dict' => array( 'port' => Port::DICT, ), 'file' => array( 'ihost' => 'localhost', ), 'http' => array( 'port' => Port::HTTP, ), 'https' => array( 'port' => Port::HTTPS, ), ); /** * Return the entire IRI when you try and read the object as a string * * @return string */ public function __toString() { return $this->get_iri(); } /** * Overload __set() to provide access via properties * * @param string $name Property name * @param mixed $value Property value */ public function __set($name, $value) { if (method_exists($this, 'set_' . $name)) { call_user_func(array($this, 'set_' . $name), $value); } elseif ( $name === 'iauthority' || $name === 'iuserinfo' || $name === 'ihost' || $name === 'ipath' || $name === 'iquery' || $name === 'ifragment' ) { call_user_func(array($this, 'set_' . substr($name, 1)), $value); } } /** * Overload __get() to provide access via properties * * @param string $name Property name * @return mixed */ public function __get($name) { // isset() returns false for null, we don't want to do that // Also why we use array_key_exists below instead of isset() $props = get_object_vars($this); if ( $name === 'iri' || $name === 'uri' || $name === 'iauthority' || $name === 'authority' ) { $method = 'get_' . $name; $return = $this->$method(); } elseif (array_key_exists($name, $props)) { $return = $this->$name; } // host -> ihost elseif (($prop = 'i' . $name) && array_key_exists($prop, $props)) { $name = $prop; $return = $this->$prop; } // ischeme -> scheme elseif (($prop = substr($name, 1)) && array_key_exists($prop, $props)) { $name = $prop; $return = $this->$prop; } else { trigger_error('Undefined property: ' . get_class($this) . '::' . $name, E_USER_NOTICE); $return = null; } if ($return === null && isset($this->scheme, $this->normalization[$this->scheme][$name])) { return $this->normalization[$this->scheme][$name]; } else { return $return; } } /** * Overload __isset() to provide access via properties * * @param string $name Property name * @return bool */ public function __isset($name) { return (method_exists($this, 'get_' . $name) || isset($this->$name)); } /** * Overload __unset() to provide access via properties * * @param string $name Property name */ public function __unset($name) { if (method_exists($this, 'set_' . $name)) { call_user_func(array($this, 'set_' . $name), ''); } } /** * Create a new IRI object, from a specified string * * @param string|Stringable|null $iri * * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $iri argument is not a string, Stringable or null. */ public function __construct($iri = null) { if ($iri !== null && InputValidator::is_string_or_stringable($iri) === false) { throw InvalidArgument::create(1, '$iri', 'string|Stringable|null', gettype($iri)); } $this->set_iri($iri); } /** * Create a new IRI object by resolving a relative IRI * * Returns false if $base is not absolute, otherwise an IRI. * * @param \WpOrg\Requests\Iri|string $base (Absolute) Base IRI * @param \WpOrg\Requests\Iri|string $relative Relative IRI * @return \WpOrg\Requests\Iri|false */ public static function absolutize($base, $relative) { if (!($relative instanceof self)) { $relative = new self($relative); } if (!$relative->is_valid()) { return false; } elseif ($relative->scheme !== null) { return clone $relative; } if (!($base instanceof self)) { $base = new self($base); } if ($base->scheme === null || !$base->is_valid()) { return false; } if ($relative->get_iri() !== '') { if ($relative->iuserinfo !== null || $relative->ihost !== null || $relative->port !== null) { $target = clone $relative; $target->scheme = $base->scheme; } else { $target = new self; $target->scheme = $base->scheme; $target->iuserinfo = $base->iuserinfo; $target->ihost = $base->ihost; $target->port = $base->port; if ($relative->ipath !== '') { if ($relative->ipath[0] === '/') { $target->ipath = $relative->ipath; } elseif (($base->iuserinfo !== null || $base->ihost !== null || $base->port !== null) && $base->ipath === '') { $target->ipath = '/' . $relative->ipath; } elseif (($last_segment = strrpos($base->ipath, '/')) !== false) { $target->ipath = substr($base->ipath, 0, $last_segment + 1) . $relative->ipath; } else { $target->ipath = $relative->ipath; } $target->ipath = $target->remove_dot_segments($target->ipath); $target->iquery = $relative->iquery; } else { $target->ipath = $base->ipath; if ($relative->iquery !== null) { $target->iquery = $relative->iquery; } elseif ($base->iquery !== null) { $target->iquery = $base->iquery; } } $target->ifragment = $relative->ifragment; } } else { $target = clone $base; $target->ifragment = null; } $target->scheme_normalization(); return $target; } /** * Parse an IRI into scheme/authority/path/query/fragment segments * * @param string $iri * @return array */ protected function parse_iri($iri) { $iri = trim($iri, "\x20\x09\x0A\x0C\x0D"); $has_match = preg_match('/^((?P[^:\/?#]+):)?(\/\/(?P[^\/?#]*))?(?P[^?#]*)(\?(?P[^#]*))?(#(?P.*))?$/', $iri, $match); if (!$has_match) { throw new Exception('Cannot parse supplied IRI', 'iri.cannot_parse', $iri); } if ($match[1] === '') { $match['scheme'] = null; } if (!isset($match[3]) || $match[3] === '') { $match['authority'] = null; } if (!isset($match[5])) { $match['path'] = ''; } if (!isset($match[6]) || $match[6] === '') { $match['query'] = null; } if (!isset($match[8]) || $match[8] === '') { $match['fragment'] = null; } return $match; } /** * Remove dot segments from a path * * @param string $input * @return string */ protected function remove_dot_segments($input) { $output = ''; while (strpos($input, './') !== false || strpos($input, '/.') !== false || $input === '.' || $input === '..') { // A: If the input buffer begins with a prefix of "../" or "./", // then remove that prefix from the input buffer; otherwise, if (strpos($input, '../') === 0) { $input = substr($input, 3); } elseif (strpos($input, './') === 0) { $input = substr($input, 2); } // B: if the input buffer begins with a prefix of "/./" or "/.", // where "." is a complete path segment, then replace that prefix // with "/" in the input buffer; otherwise, elseif (strpos($input, '/./') === 0) { $input = substr($input, 2); } elseif ($input === '/.') { $input = '/'; } // C: if the input buffer begins with a prefix of "/../" or "/..", // where ".." is a complete path segment, then replace that prefix // with "/" in the input buffer and remove the last segment and its // preceding "/" (if any) from the output buffer; otherwise, elseif (strpos($input, '/../') === 0) { $input = substr($input, 3); $output = substr_replace($output, '', (strrpos($output, '/') ?: 0)); } elseif ($input === '/..') { $input = '/'; $output = substr_replace($output, '', (strrpos($output, '/') ?: 0)); } // D: if the input buffer consists only of "." or "..", then remove // that from the input buffer; otherwise, elseif ($input === '.' || $input === '..') { $input = ''; } // E: move the first path segment in the input buffer to the end of // the output buffer, including the initial "/" character (if any) // and any subsequent characters up to, but not including, the next // "/" character or the end of the input buffer elseif (($pos = strpos($input, '/', 1)) !== false) { $output .= substr($input, 0, $pos); $input = substr_replace($input, '', 0, $pos); } else { $output .= $input; $input = ''; } } return $output . $input; } /** * Replace invalid character with percent encoding * * @param string $text Input string * @param string $extra_chars Valid characters not in iunreserved or * iprivate (this is ASCII-only) * @param bool $iprivate Allow iprivate * @return string */ protected function replace_invalid_with_pct_encoding($text, $extra_chars, $iprivate = false) { // Normalize as many pct-encoded sections as possible $text = preg_replace_callback('/(?:%[A-Fa-f0-9]{2})+/', array($this, 'remove_iunreserved_percent_encoded'), $text); // Replace invalid percent characters $text = preg_replace('/%(?![A-Fa-f0-9]{2})/', '%25', $text); // Add unreserved and % to $extra_chars (the latter is safe because all // pct-encoded sections are now valid). $extra_chars .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~%'; // Now replace any bytes that aren't allowed with their pct-encoded versions $position = 0; $strlen = strlen($text); while (($position += strspn($text, $extra_chars, $position)) < $strlen) { $value = ord($text[$position]); // Start position $start = $position; // By default we are valid $valid = true; // No one byte sequences are valid due to the while. // Two byte sequence: if (($value & 0xE0) === 0xC0) { $character = ($value & 0x1F) << 6; $length = 2; $remaining = 1; } // Three byte sequence: elseif (($value & 0xF0) === 0xE0) { $character = ($value & 0x0F) << 12; $length = 3; $remaining = 2; } // Four byte sequence: elseif (($value & 0xF8) === 0xF0) { $character = ($value & 0x07) << 18; $length = 4; $remaining = 3; } // Invalid byte: else { $valid = false; $length = 1; $remaining = 0; } if ($remaining) { if ($position + $length <= $strlen) { for ($position++; $remaining; $position++) { $value = ord($text[$position]); // Check that the byte is valid, then add it to the character: if (($value & 0xC0) === 0x80) { $character |= ($value & 0x3F) << (--$remaining * 6); } // If it is invalid, count the sequence as invalid and reprocess the current byte: else { $valid = false; $position--; break; } } } else { $position = $strlen - 1; $valid = false; } } // Percent encode anything invalid or not in ucschar if ( // Invalid sequences !$valid // Non-shortest form sequences are invalid || $length > 1 && $character <= 0x7F || $length > 2 && $character <= 0x7FF || $length > 3 && $character <= 0xFFFF // Outside of range of ucschar codepoints // Noncharacters || ($character & 0xFFFE) === 0xFFFE || $character >= 0xFDD0 && $character <= 0xFDEF || ( // Everything else not in ucschar $character > 0xD7FF && $character < 0xF900 || $character < 0xA0 || $character > 0xEFFFD ) && ( // Everything not in iprivate, if it applies !$iprivate || $character < 0xE000 || $character > 0x10FFFD ) ) { // If we were a character, pretend we weren't, but rather an error. if ($valid) { $position--; } for ($j = $start; $j <= $position; $j++) { $text = substr_replace($text, sprintf('%%%02X', ord($text[$j])), $j, 1); $j += 2; $position += 2; $strlen += 2; } } } return $text; } /** * Callback function for preg_replace_callback. * * Removes sequences of percent encoded bytes that represent UTF-8 * encoded characters in iunreserved * * @param array $regex_match PCRE match * @return string Replacement */ protected function remove_iunreserved_percent_encoded($regex_match) { // As we just have valid percent encoded sequences we can just explode // and ignore the first member of the returned array (an empty string). $bytes = explode('%', $regex_match[0]); // Initialize the new string (this is what will be returned) and that // there are no bytes remaining in the current sequence (unsurprising // at the first byte!). $string = ''; $remaining = 0; // Loop over each and every byte, and set $value to its value for ($i = 1, $len = count($bytes); $i < $len; $i++) { $value = hexdec($bytes[$i]); // If we're the first byte of sequence: if (!$remaining) { // Start position $start = $i; // By default we are valid $valid = true; // One byte sequence: if ($value <= 0x7F) { $character = $value; $length = 1; } // Two byte sequence: elseif (($value & 0xE0) === 0xC0) { $character = ($value & 0x1F) << 6; $length = 2; $remaining = 1; } // Three byte sequence: elseif (($value & 0xF0) === 0xE0) { $character = ($value & 0x0F) << 12; $length = 3; $remaining = 2; } // Four byte sequence: elseif (($value & 0xF8) === 0xF0) { $character = ($value & 0x07) << 18; $length = 4; $remaining = 3; } // Invalid byte: else { $valid = false; $remaining = 0; } } // Continuation byte: else { // Check that the byte is valid, then add it to the character: if (($value & 0xC0) === 0x80) { $remaining--; $character |= ($value & 0x3F) << ($remaining * 6); } // If it is invalid, count the sequence as invalid and reprocess the current byte as the start of a sequence: else { $valid = false; $remaining = 0; $i--; } } // If we've reached the end of the current byte sequence, append it to Unicode::$data if (!$remaining) { // Percent encode anything invalid or not in iunreserved if ( // Invalid sequences !$valid // Non-shortest form sequences are invalid || $length > 1 && $character <= 0x7F || $length > 2 && $character <= 0x7FF || $length > 3 && $character <= 0xFFFF // Outside of range of iunreserved codepoints || $character < 0x2D || $character > 0xEFFFD // Noncharacters || ($character & 0xFFFE) === 0xFFFE || $character >= 0xFDD0 && $character <= 0xFDEF // Everything else not in iunreserved (this is all BMP) || $character === 0x2F || $character > 0x39 && $character < 0x41 || $character > 0x5A && $character < 0x61 || $character > 0x7A && $character < 0x7E || $character > 0x7E && $character < 0xA0 || $character > 0xD7FF && $character < 0xF900 ) { for ($j = $start; $j <= $i; $j++) { $string .= '%' . strtoupper($bytes[$j]); } } else { for ($j = $start; $j <= $i; $j++) { $string .= chr(hexdec($bytes[$j])); } } } } // If we have any bytes left over they are invalid (i.e., we are // mid-way through a multi-byte sequence) if ($remaining) { for ($j = $start; $j < $len; $j++) { $string .= '%' . strtoupper($bytes[$j]); } } return $string; } protected function scheme_normalization() { if (isset($this->scheme, $this->normalization[$this->scheme])) { if (isset($this->normalization[$this->scheme]['iuserinfo']) && $this->iuserinfo === $this->normalization[$this->scheme]['iuserinfo']) { $this->iuserinfo = null; } if (isset($this->normalization[$this->scheme]['ihost']) && $this->ihost === $this->normalization[$this->scheme]['ihost']) { $this->ihost = null; } if (isset($this->normalization[$this->scheme]['port']) && $this->port === $this->normalization[$this->scheme]['port']) { $this->port = null; } if (isset($this->normalization[$this->scheme]['ipath']) && $this->ipath === $this->normalization[$this->scheme]['ipath']) { $this->ipath = ''; } if (isset($this->normalization[$this->scheme]['iquery']) && $this->iquery === $this->normalization[$this->scheme]['iquery']) { $this->iquery = null; } if (isset($this->normalization[$this->scheme]['ifragment']) && $this->ifragment === $this->normalization[$this->scheme]['ifragment']) { $this->ifragment = null; } } if (isset($this->ihost) && empty($this->ipath)) { $this->ipath = '/'; } } /** * Check if the object represents a valid IRI. This needs to be done on each * call as some things change depending on another part of the IRI. * * @return bool */ public function is_valid() { $isauthority = $this->iuserinfo !== null || $this->ihost !== null || $this->port !== null; if ($this->ipath !== '' && ( $isauthority && $this->ipath[0] !== '/' || ( $this->scheme === null && !$isauthority && strpos($this->ipath, ':') !== false && (strpos($this->ipath, '/') === false ? true : strpos($this->ipath, ':') < strpos($this->ipath, '/')) ) ) ) { return false; } return true; } public function __wakeup() { $class_props = get_class_vars( __CLASS__ ); $string_props = array( 'scheme', 'iuserinfo', 'ihost', 'port', 'ipath', 'iquery', 'ifragment' ); $array_props = array( 'normalization' ); foreach ( $class_props as $prop => $default_value ) { if ( in_array( $prop, $string_props, true ) && ! is_string( $this->$prop ) ) { throw new UnexpectedValueException(); } elseif ( in_array( $prop, $array_props, true ) && ! is_array( $this->$prop ) ) { throw new UnexpectedValueException(); } $this->$prop = null; } } /** * Set the entire IRI. Returns true on success, false on failure (if there * are any invalid characters). * * @param string $iri * @return bool */ protected function set_iri($iri) { static $cache; if (!$cache) { $cache = array(); } if ($iri === null) { return true; } $iri = (string) $iri; if (isset($cache[$iri])) { list($this->scheme, $this->iuserinfo, $this->ihost, $this->port, $this->ipath, $this->iquery, $this->ifragment, $return) = $cache[$iri]; return $return; } $parsed = $this->parse_iri($iri); $return = $this->set_scheme($parsed['scheme']) && $this->set_authority($parsed['authority']) && $this->set_path($parsed['path']) && $this->set_query($parsed['query']) && $this->set_fragment($parsed['fragment']); $cache[$iri] = array($this->scheme, $this->iuserinfo, $this->ihost, $this->port, $this->ipath, $this->iquery, $this->ifragment, $return); return $return; } /** * Set the scheme. Returns true on success, false on failure (if there are * any invalid characters). * * @param string $scheme * @return bool */ protected function set_scheme($scheme) { if ($scheme === null) { $this->scheme = null; } elseif (!preg_match('/^[A-Za-z][0-9A-Za-z+\-.]*$/', $scheme)) { $this->scheme = null; return false; } else { $this->scheme = strtolower($scheme); } return true; } /** * Set the authority. Returns true on success, false on failure (if there are * any invalid characters). * * @param string $authority * @return bool */ protected function set_authority($authority) { static $cache; if (!$cache) { $cache = array(); } if ($authority === null) { $this->iuserinfo = null; $this->ihost = null; $this->port = null; return true; } if (isset($cache[$authority])) { list($this->iuserinfo, $this->ihost, $this->port, $return) = $cache[$authority]; return $return; } $remaining = $authority; if (($iuserinfo_end = strrpos($remaining, '@')) !== false) { $iuserinfo = substr($remaining, 0, $iuserinfo_end); $remaining = substr($remaining, $iuserinfo_end + 1); } else { $iuserinfo = null; } if (($port_start = strpos($remaining, ':', (strpos($remaining, ']') ?: 0))) !== false) { $port = substr($remaining, $port_start + 1); if ($port === false || $port === '') { $port = null; } $remaining = substr($remaining, 0, $port_start); } else { $port = null; } $return = $this->set_userinfo($iuserinfo) && $this->set_host($remaining) && $this->set_port($port); $cache[$authority] = array($this->iuserinfo, $this->ihost, $this->port, $return); return $return; } /** * Set the iuserinfo. * * @param string $iuserinfo * @return bool */ protected function set_userinfo($iuserinfo) { if ($iuserinfo === null) { $this->iuserinfo = null; } else { $this->iuserinfo = $this->replace_invalid_with_pct_encoding($iuserinfo, '!$&\'()*+,;=:'); $this->scheme_normalization(); } return true; } /** * Set the ihost. Returns true on success, false on failure (if there are * any invalid characters). * * @param string $ihost * @return bool */ protected function set_host($ihost) { if ($ihost === null) { $this->ihost = null; return true; } if (substr($ihost, 0, 1) === '[' && substr($ihost, -1) === ']') { if (Ipv6::check_ipv6(substr($ihost, 1, -1))) { $this->ihost = '[' . Ipv6::compress(substr($ihost, 1, -1)) . ']'; } else { $this->ihost = null; return false; } } else { $ihost = $this->replace_invalid_with_pct_encoding($ihost, '!$&\'()*+,;='); // Lowercase, but ignore pct-encoded sections (as they should // remain uppercase). This must be done after the previous step // as that can add unescaped characters. $position = 0; $strlen = strlen($ihost); while (($position += strcspn($ihost, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ%', $position)) < $strlen) { if ($ihost[$position] === '%') { $position += 3; } else { $ihost[$position] = strtolower($ihost[$position]); $position++; } } $this->ihost = $ihost; } $this->scheme_normalization(); return true; } /** * Set the port. Returns true on success, false on failure (if there are * any invalid characters). * * @param string $port * @return bool */ protected function set_port($port) { if ($port === null) { $this->port = null; return true; } if (strspn($port, '0123456789') === strlen($port)) { $this->port = (int) $port; $this->scheme_normalization(); return true; } $this->port = null; return false; } /** * Set the ipath. * * @param string $ipath * @return bool */ protected function set_path($ipath) { static $cache; if (!$cache) { $cache = array(); } $ipath = (string) $ipath; if (isset($cache[$ipath])) { $this->ipath = $cache[$ipath][(int) ($this->scheme !== null)]; } else { $valid = $this->replace_invalid_with_pct_encoding($ipath, '!$&\'()*+,;=@:/'); $removed = $this->remove_dot_segments($valid); $cache[$ipath] = array($valid, $removed); $this->ipath = ($this->scheme !== null) ? $removed : $valid; } $this->scheme_normalization(); return true; } /** * Set the iquery. * * @param string $iquery * @return bool */ protected function set_query($iquery) { if ($iquery === null) { $this->iquery = null; } else { $this->iquery = $this->replace_invalid_with_pct_encoding($iquery, '!$&\'()*+,;=:@/?', true); $this->scheme_normalization(); } return true; } /** * Set the ifragment. * * @param string $ifragment * @return bool */ protected function set_fragment($ifragment) { if ($ifragment === null) { $this->ifragment = null; } else { $this->ifragment = $this->replace_invalid_with_pct_encoding($ifragment, '!$&\'()*+,;=:@/?'); $this->scheme_normalization(); } return true; } /** * Convert an IRI to a URI (or parts thereof) * * @param string|bool $iri IRI to convert (or false from {@see \WpOrg\Requests\Iri::get_iri()}) * @return string|false URI if IRI is valid, false otherwise. */ protected function to_uri($iri) { if (!is_string($iri)) { return false; } static $non_ascii; if (!$non_ascii) { $non_ascii = implode('', range("\x80", "\xFF")); } $position = 0; $strlen = strlen($iri); while (($position += strcspn($iri, $non_ascii, $position)) < $strlen) { $iri = substr_replace($iri, sprintf('%%%02X', ord($iri[$position])), $position, 1); $position += 3; $strlen += 2; } return $iri; } /** * Get the complete IRI * * @return string|false */ protected function get_iri() { if (!$this->is_valid()) { return false; } $iri = ''; if ($this->scheme !== null) { $iri .= $this->scheme . ':'; } if (($iauthority = $this->get_iauthority()) !== null) { $iri .= '//' . $iauthority; } $iri .= $this->ipath; if ($this->iquery !== null) { $iri .= '?' . $this->iquery; } if ($this->ifragment !== null) { $iri .= '#' . $this->ifragment; } return $iri; } /** * Get the complete URI * * @return string */ protected function get_uri() { return $this->to_uri($this->get_iri()); } /** * Get the complete iauthority * * @return string|null */ protected function get_iauthority() { if ($this->iuserinfo === null && $this->ihost === null && $this->port === null) { return null; } $iauthority = ''; if ($this->iuserinfo !== null) { $iauthority .= $this->iuserinfo . '@'; } if ($this->ihost !== null) { $iauthority .= $this->ihost; } if ($this->port !== null) { $iauthority .= ':' . $this->port; } return $iauthority; } /** * Get the complete authority * * @return string */ protected function get_authority() { $iauthority = $this->get_iauthority(); if (is_string($iauthority)) { return $this->to_uri($iauthority); } else { return $iauthority; } } } Proxy.php000064400000001543152505214730006405 0ustar00data[$offset])) { return null; } return $this->flatten($this->data[$offset]); } /** * Set the given item * * @param string $offset Item name * @param string $value Item value * * @throws \WpOrg\Requests\Exception On attempting to use dictionary as list (`invalidset`) */ public function offsetSet($offset, $value) { if ($offset === null) { throw new Exception('Object is a dictionary, not a list', 'invalidset'); } if (is_string($offset)) { $offset = strtolower($offset); } if (!isset($this->data[$offset])) { $this->data[$offset] = []; } $this->data[$offset][] = $value; } /** * Get all values for a given header * * @param string $offset Name of the header to retrieve. * @return array|null Header values * * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not valid as an array key. */ public function getValues($offset) { if (!is_string($offset) && !is_int($offset)) { throw InvalidArgument::create(1, '$offset', 'string|int', gettype($offset)); } if (is_string($offset)) { $offset = strtolower($offset); } if (!isset($this->data[$offset])) { return null; } return $this->data[$offset]; } /** * Flattens a value into a string * * Converts an array into a string by imploding values with a comma, as per * RFC2616's rules for folding headers. * * @param string|array $value Value to flatten * @return string Flattened value * * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not a string or an array. */ public function flatten($value) { if (is_string($value)) { return $value; } if (is_array($value)) { return implode(',', $value); } throw InvalidArgument::create(1, '$value', 'string|array', gettype($value)); } /** * Get an iterator for the data * * Converts the internally stored values to a comma-separated string if there is more * than one value for a key. * * @return \ArrayIterator */ public function getIterator() { return new FilteredIterator($this->data, [$this, 'flatten']); } } Requests.php000064400000102321152505214730007073 0ustar00 10, 'connect_timeout' => 10, 'useragent' => 'php-requests/' . self::VERSION, 'protocol_version' => 1.1, 'redirected' => 0, 'redirects' => 10, 'follow_redirects' => true, 'blocking' => true, 'type' => self::GET, 'filename' => false, 'auth' => false, 'proxy' => false, 'cookies' => false, 'max_bytes' => false, 'idn' => true, 'hooks' => null, 'transport' => null, 'verify' => null, 'verifyname' => true, ]; /** * Default supported Transport classes. * * @since 2.0.0 * * @var array */ const DEFAULT_TRANSPORTS = [ Curl::class => Curl::class, Fsockopen::class => Fsockopen::class, ]; /** * Current version of Requests * * @var string */ const VERSION = '2.0.17'; /** * Selected transport name * * Use {@see \WpOrg\Requests\Requests::get_transport()} instead * * @var array */ public static $transport = []; /** * Registered transport classes * * @var array */ protected static $transports = []; /** * Default certificate path. * * @see \WpOrg\Requests\Requests::get_certificate_path() * @see \WpOrg\Requests\Requests::set_certificate_path() * * @var string */ protected static $certificate_path = __DIR__ . '/../certificates/cacert.pem'; /** * All (known) valid deflate, gzip header magic markers. * * These markers relate to different compression levels. * * @link https://stackoverflow.com/a/43170354/482864 Marker source. * * @since 2.0.0 * * @var array */ private static $magic_compression_headers = [ "\x1f\x8b" => true, // Gzip marker. "\x78\x01" => true, // Zlib marker - level 1. "\x78\x5e" => true, // Zlib marker - level 2 to 5. "\x78\x9c" => true, // Zlib marker - level 6. "\x78\xda" => true, // Zlib marker - level 7 to 9. ]; /** * This is a static class, do not instantiate it * * @codeCoverageIgnore */ private function __construct() {} /** * Register a transport * * @param string $transport Transport class to add, must support the \WpOrg\Requests\Transport interface */ public static function add_transport($transport) { if (empty(self::$transports)) { self::$transports = self::DEFAULT_TRANSPORTS; } self::$transports[$transport] = $transport; } /** * Get the fully qualified class name (FQCN) for a working transport. * * @param array $capabilities Optional. Associative array of capabilities to test against, i.e. `['' => true]`. * @return string FQCN of the transport to use, or an empty string if no transport was * found which provided the requested capabilities. */ protected static function get_transport_class(array $capabilities = []) { // Caching code, don't bother testing coverage. // @codeCoverageIgnoreStart // Array of capabilities as a string to be used as an array key. ksort($capabilities); $cap_string = serialize($capabilities); // Don't search for a transport if it's already been done for these $capabilities. if (isset(self::$transport[$cap_string])) { return self::$transport[$cap_string]; } // Ensure we will not run this same check again later on. self::$transport[$cap_string] = ''; // @codeCoverageIgnoreEnd if (empty(self::$transports)) { self::$transports = self::DEFAULT_TRANSPORTS; } // Find us a working transport. foreach (self::$transports as $class) { if (!class_exists($class)) { continue; } $result = $class::test($capabilities); if ($result === true) { self::$transport[$cap_string] = $class; break; } } return self::$transport[$cap_string]; } /** * Get a working transport. * * @param array $capabilities Optional. Associative array of capabilities to test against, i.e. `['' => true]`. * @return \WpOrg\Requests\Transport * @throws \WpOrg\Requests\Exception If no valid transport is found (`notransport`). */ protected static function get_transport(array $capabilities = []) { $class = self::get_transport_class($capabilities); if ($class === '') { throw new Exception('No working transports found', 'notransport', self::$transports); } return new $class(); } /** * Checks to see if we have a transport for the capabilities requested. * * Supported capabilities can be found in the {@see \WpOrg\Requests\Capability} * interface as constants. * * Example usage: * `Requests::has_capabilities([Capability::SSL => true])`. * * @param array $capabilities Optional. Associative array of capabilities to test against, i.e. `['' => true]`. * @return bool Whether the transport has the requested capabilities. */ public static function has_capabilities(array $capabilities = []) { return self::get_transport_class($capabilities) !== ''; } /**#@+ * @see \WpOrg\Requests\Requests::request() * @param string $url * @param array $headers * @param array $options * @return \WpOrg\Requests\Response */ /** * Send a GET request */ public static function get($url, $headers = [], $options = []) { return self::request($url, $headers, null, self::GET, $options); } /** * Send a HEAD request */ public static function head($url, $headers = [], $options = []) { return self::request($url, $headers, null, self::HEAD, $options); } /** * Send a DELETE request */ public static function delete($url, $headers = [], $options = []) { return self::request($url, $headers, null, self::DELETE, $options); } /** * Send a TRACE request */ public static function trace($url, $headers = [], $options = []) { return self::request($url, $headers, null, self::TRACE, $options); } /**#@-*/ /**#@+ * @see \WpOrg\Requests\Requests::request() * @param string $url * @param array $headers * @param array $data * @param array $options * @return \WpOrg\Requests\Response */ /** * Send a POST request */ public static function post($url, $headers = [], $data = [], $options = []) { return self::request($url, $headers, $data, self::POST, $options); } /** * Send a PUT request */ public static function put($url, $headers = [], $data = [], $options = []) { return self::request($url, $headers, $data, self::PUT, $options); } /** * Send an OPTIONS request */ public static function options($url, $headers = [], $data = [], $options = []) { return self::request($url, $headers, $data, self::OPTIONS, $options); } /** * Send a PATCH request * * Note: Unlike {@see \WpOrg\Requests\Requests::post()} and {@see \WpOrg\Requests\Requests::put()}, * `$headers` is required, as the specification recommends that should send an ETag * * @link https://tools.ietf.org/html/rfc5789 */ public static function patch($url, $headers, $data = [], $options = []) { return self::request($url, $headers, $data, self::PATCH, $options); } /**#@-*/ /** * Main interface for HTTP requests * * This method initiates a request and sends it via a transport before * parsing. * * The `$options` parameter takes an associative array with the following * options: * * - `timeout`: How long should we wait for a response? * Note: for cURL, a minimum of 1 second applies, as DNS resolution * operates at second-resolution only. * (float, seconds with a millisecond precision, default: 10, example: 0.01) * - `connect_timeout`: How long should we wait while trying to connect? * (float, seconds with a millisecond precision, default: 10, example: 0.01) * - `useragent`: Useragent to send to the server * (string, default: php-requests/$version) * - `follow_redirects`: Should we follow 3xx redirects? * (boolean, default: true) * - `redirects`: How many times should we redirect before erroring? * (integer, default: 10) * - `blocking`: Should we block processing on this request? * (boolean, default: true) * - `filename`: File to stream the body to instead. * (string|boolean, default: false) * - `auth`: Authentication handler or array of user/password details to use * for Basic authentication * (\WpOrg\Requests\Auth|array|boolean, default: false) * - `proxy`: Proxy details to use for proxy by-passing and authentication * (\WpOrg\Requests\Proxy|array|string|boolean, default: false) * - `max_bytes`: Limit for the response body size. * (integer|boolean, default: false) * - `idn`: Enable IDN parsing * (boolean, default: true) * - `transport`: Custom transport. Either a class name, or a * transport object. Defaults to the first working transport from * {@see \WpOrg\Requests\Requests::getTransport()} * (string|\WpOrg\Requests\Transport, default: {@see \WpOrg\Requests\Requests::getTransport()}) * - `hooks`: Hooks handler. * (\WpOrg\Requests\HookManager, default: new WpOrg\Requests\Hooks()) * - `verify`: Should we verify SSL certificates? Allows passing in a custom * certificate file as a string. (Using true uses the system-wide root * certificate store instead, but this may have different behaviour * across transports.) * (string|boolean, default: certificates/cacert.pem) * - `verifyname`: Should we verify the common name in the SSL certificate? * (boolean, default: true) * - `data_format`: How should we send the `$data` parameter? * (string, one of 'query' or 'body', default: 'query' for * HEAD/GET/DELETE, 'body' for POST/PUT/OPTIONS/PATCH) * * @param string|Stringable $url URL to request * @param array $headers Extra headers to send with the request * @param array|null $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests * @param string $type HTTP request type (use Requests constants) * @param array $options Options for the request (see description for more information) * @return \WpOrg\Requests\Response * * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $url argument is not a string or Stringable. * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $type argument is not a string. * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array. * @throws \WpOrg\Requests\Exception On invalid URLs (`nonhttp`) */ public static function request($url, $headers = [], $data = [], $type = self::GET, $options = []) { if (InputValidator::is_string_or_stringable($url) === false) { throw InvalidArgument::create(1, '$url', 'string|Stringable', gettype($url)); } if (is_string($type) === false) { throw InvalidArgument::create(4, '$type', 'string', gettype($type)); } if (is_array($options) === false) { throw InvalidArgument::create(5, '$options', 'array', gettype($options)); } if (empty($options['type'])) { $options['type'] = $type; } $options = array_merge(self::get_default_options(), $options); self::set_defaults($url, $headers, $data, $type, $options); $options['hooks']->dispatch('requests.before_request', [&$url, &$headers, &$data, &$type, &$options]); if (!empty($options['transport'])) { $transport = $options['transport']; if (is_string($options['transport'])) { $transport = new $transport(); } } else { $need_ssl = (stripos($url, 'https://') === 0); $capabilities = [Capability::SSL => $need_ssl]; $transport = self::get_transport($capabilities); } $response = $transport->request($url, $headers, $data, $options); $options['hooks']->dispatch('requests.before_parse', [&$response, $url, $headers, $data, $type, $options]); return self::parse_response($response, $url, $headers, $data, $options); } /** * Send multiple HTTP requests simultaneously * * The `$requests` parameter takes an associative or indexed array of * request fields. The key of each request can be used to match up the * request with the returned data, or with the request passed into your * `multiple.request.complete` callback. * * The request fields value is an associative array with the following keys: * * - `url`: Request URL Same as the `$url` parameter to * {@see \WpOrg\Requests\Requests::request()} * (string, required) * - `headers`: Associative array of header fields. Same as the `$headers` * parameter to {@see \WpOrg\Requests\Requests::request()} * (array, default: `array()`) * - `data`: Associative array of data fields or a string. Same as the * `$data` parameter to {@see \WpOrg\Requests\Requests::request()} * (array|string, default: `array()`) * - `type`: HTTP request type (use \WpOrg\Requests\Requests constants). Same as the `$type` * parameter to {@see \WpOrg\Requests\Requests::request()} * (string, default: `\WpOrg\Requests\Requests::GET`) * - `cookies`: Associative array of cookie name to value, or cookie jar. * (array|\WpOrg\Requests\Cookie\Jar) * * If the `$options` parameter is specified, individual requests will * inherit options from it. This can be used to use a single hooking system, * or set all the types to `\WpOrg\Requests\Requests::POST`, for example. * * In addition, the `$options` parameter takes the following global options: * * - `complete`: A callback for when a request is complete. Takes two * parameters, a \WpOrg\Requests\Response/\WpOrg\Requests\Exception reference, and the * ID from the request array (Note: this can also be overridden on a * per-request basis, although that's a little silly) * (callback) * * @param array $requests Requests data (see description for more information) * @param array $options Global and default options (see {@see \WpOrg\Requests\Requests::request()}) * @return array Responses (either \WpOrg\Requests\Response or a \WpOrg\Requests\Exception object) * * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $requests argument is not an array or iterable object with array access. * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array. */ public static function request_multiple($requests, $options = []) { if (InputValidator::has_array_access($requests) === false || InputValidator::is_iterable($requests) === false) { throw InvalidArgument::create(1, '$requests', 'array|ArrayAccess&Traversable', gettype($requests)); } if (is_array($options) === false) { throw InvalidArgument::create(2, '$options', 'array', gettype($options)); } $options = array_merge(self::get_default_options(true), $options); if (!empty($options['hooks'])) { $options['hooks']->register('transport.internal.parse_response', [static::class, 'parse_multiple']); if (!empty($options['complete'])) { $options['hooks']->register('multiple.request.complete', $options['complete']); } } foreach ($requests as $id => &$request) { if (!isset($request['headers'])) { $request['headers'] = []; } if (!isset($request['data'])) { $request['data'] = []; } if (!isset($request['type'])) { $request['type'] = self::GET; } if (!isset($request['options'])) { $request['options'] = $options; $request['options']['type'] = $request['type']; } else { if (empty($request['options']['type'])) { $request['options']['type'] = $request['type']; } $request['options'] = array_merge($options, $request['options']); } self::set_defaults($request['url'], $request['headers'], $request['data'], $request['type'], $request['options']); // Ensure we only hook in once if ($request['options']['hooks'] !== $options['hooks']) { $request['options']['hooks']->register('transport.internal.parse_response', [static::class, 'parse_multiple']); if (!empty($request['options']['complete'])) { $request['options']['hooks']->register('multiple.request.complete', $request['options']['complete']); } } } unset($request); if (!empty($options['transport'])) { $transport = $options['transport']; if (is_string($options['transport'])) { $transport = new $transport(); } } else { $transport = self::get_transport(); } $responses = $transport->request_multiple($requests, $options); foreach ($responses as $id => &$response) { // If our hook got messed with somehow, ensure we end up with the // correct response if (is_string($response)) { $request = $requests[$id]; self::parse_multiple($response, $request); $request['options']['hooks']->dispatch('multiple.request.complete', [&$response, $id]); } } return $responses; } /** * Get the default options * * @see \WpOrg\Requests\Requests::request() for values returned by this method * @param boolean $multirequest Is this a multirequest? * @return array Default option values */ protected static function get_default_options($multirequest = false) { $defaults = static::OPTION_DEFAULTS; $defaults['verify'] = self::$certificate_path; if ($multirequest !== false) { $defaults['complete'] = null; } return $defaults; } /** * Get default certificate path. * * @return string Default certificate path. */ public static function get_certificate_path() { return self::$certificate_path; } /** * Set default certificate path. * * @param string|Stringable|bool $path Certificate path, pointing to a PEM file. * * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $url argument is not a string, Stringable or boolean. */ public static function set_certificate_path($path) { if (InputValidator::is_string_or_stringable($path) === false && is_bool($path) === false) { throw InvalidArgument::create(1, '$path', 'string|Stringable|bool', gettype($path)); } self::$certificate_path = $path; } /** * Set the default values * * The $options parameter is updated with the results. * * @param string $url URL to request * @param array $headers Extra headers to send with the request * @param array|null $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests * @param string $type HTTP request type * @param array $options Options for the request * @return void * * @throws \WpOrg\Requests\Exception When the $url is not an http(s) URL. */ protected static function set_defaults(&$url, &$headers, &$data, &$type, &$options) { if (!preg_match('/^http(s)?:\/\//i', $url, $matches)) { throw new Exception('Only HTTP(S) requests are handled.', 'nonhttp', $url); } if (empty($options['hooks'])) { $options['hooks'] = new Hooks(); } if (is_array($options['auth'])) { $options['auth'] = new Basic($options['auth']); } if ($options['auth'] !== false) { $options['auth']->register($options['hooks']); } if (is_string($options['proxy']) || is_array($options['proxy'])) { $options['proxy'] = new Http($options['proxy']); } if ($options['proxy'] !== false) { $options['proxy']->register($options['hooks']); } if (is_array($options['cookies'])) { $options['cookies'] = new Jar($options['cookies']); } elseif (empty($options['cookies'])) { $options['cookies'] = new Jar(); } if ($options['cookies'] !== false) { $options['cookies']->register($options['hooks']); } if ($options['idn'] !== false) { $iri = new Iri($url); $iri->host = IdnaEncoder::encode($iri->ihost); $url = $iri->uri; } // Massage the type to ensure we support it. $type = strtoupper($type); if (!isset($options['data_format'])) { if (in_array($type, [self::HEAD, self::GET, self::DELETE], true)) { $options['data_format'] = 'query'; } else { $options['data_format'] = 'body'; } } } /** * HTTP response parser * * @param string $headers Full response text including headers and body * @param string $url Original request URL * @param array $req_headers Original $headers array passed to {@link request()}, in case we need to follow redirects * @param array $req_data Original $data array passed to {@link request()}, in case we need to follow redirects * @param array $options Original $options array passed to {@link request()}, in case we need to follow redirects * @return \WpOrg\Requests\Response * * @throws \WpOrg\Requests\Exception On missing head/body separator (`requests.no_crlf_separator`) * @throws \WpOrg\Requests\Exception On missing head/body separator (`noversion`) * @throws \WpOrg\Requests\Exception On missing head/body separator (`toomanyredirects`) */ protected static function parse_response($headers, $url, $req_headers, $req_data, $options) { $return = new Response(); if (!$options['blocking']) { return $return; } $return->raw = $headers; $return->url = (string) $url; $return->body = ''; if (!$options['filename']) { $pos = strpos($headers, "\r\n\r\n"); if ($pos === false) { // Crap! throw new Exception('Missing header/body separator', 'requests.no_crlf_separator'); } $headers = substr($return->raw, 0, $pos); // Headers will always be separated from the body by two new lines - `\n\r\n\r`. $body = substr($return->raw, $pos + 4); if (!empty($body)) { $return->body = $body; } } // Pretend CRLF = LF for compatibility (RFC 2616, section 19.3) $headers = str_replace("\r\n", "\n", $headers); // Unfold headers (replace [CRLF] 1*( SP | HT ) with SP) as per RFC 2616 (section 2.2) $headers = preg_replace('/\n[ \t]/', ' ', $headers); $headers = explode("\n", $headers); preg_match('#^HTTP/(1\.\d)[ \t]+(\d+)#i', array_shift($headers), $matches); if (empty($matches)) { throw new Exception('Response could not be parsed', 'noversion', $headers); } $return->protocol_version = (float) $matches[1]; $return->status_code = (int) $matches[2]; if ($return->status_code >= 200 && $return->status_code < 300) { $return->success = true; } foreach ($headers as $header) { list($key, $value) = explode(':', $header, 2); $value = trim($value); preg_replace('#(\s+)#i', ' ', $value); $return->headers[$key] = $value; } if (isset($return->headers['transfer-encoding'])) { $return->body = self::decode_chunked($return->body); unset($return->headers['transfer-encoding']); } if (isset($return->headers['content-encoding'])) { $return->body = self::decompress($return->body); } //fsockopen and cURL compatibility if (isset($return->headers['connection'])) { unset($return->headers['connection']); } $options['hooks']->dispatch('requests.before_redirect_check', [&$return, $req_headers, $req_data, $options]); if ($return->is_redirect() && $options['follow_redirects'] === true) { if (isset($return->headers['location']) && $options['redirected'] < $options['redirects']) { if ($return->status_code === 303) { $options['type'] = self::GET; } $options['redirected']++; $location = $return->headers['location']; if (strpos($location, 'http://') !== 0 && strpos($location, 'https://') !== 0) { // relative redirect, for compatibility make it absolute $location = Iri::absolutize($url, $location); $location = $location->uri; } $hook_args = [ &$location, &$req_headers, &$req_data, &$options, $return, ]; $options['hooks']->dispatch('requests.before_redirect', $hook_args); $redirected = self::request($location, $req_headers, $req_data, $options['type'], $options); $redirected->history[] = $return; return $redirected; } elseif ($options['redirected'] >= $options['redirects']) { throw new Exception('Too many redirects', 'toomanyredirects', $return); } } $return->redirects = $options['redirected']; $options['hooks']->dispatch('requests.after_request', [&$return, $req_headers, $req_data, $options]); return $return; } /** * Callback for `transport.internal.parse_response` * * Internal use only. Converts a raw HTTP response to a \WpOrg\Requests\Response * while still executing a multiple request. * * `$response` is either set to a \WpOrg\Requests\Response instance, or a \WpOrg\Requests\Exception object * * @param string $response Full response text including headers and body (will be overwritten with Response instance) * @param array $request Request data as passed into {@see \WpOrg\Requests\Requests::request_multiple()} * @return void */ public static function parse_multiple(&$response, $request) { try { $url = $request['url']; $headers = $request['headers']; $data = $request['data']; $options = $request['options']; $response = self::parse_response($response, $url, $headers, $data, $options); } catch (Exception $e) { $response = $e; } } /** * Decoded a chunked body as per RFC 2616 * * @link https://tools.ietf.org/html/rfc2616#section-3.6.1 * @param string $data Chunked body * @return string Decoded body */ protected static function decode_chunked($data) { if (!preg_match('/^([0-9a-f]+)(?:;(?:[\w-]*)(?:=(?:(?:[\w-]*)*|"(?:[^\r\n])*"))?)*\r\n/i', trim($data))) { return $data; } $decoded = ''; $encoded = $data; while (true) { $is_chunked = (bool) preg_match('/^([0-9a-f]+)(?:;(?:[\w-]*)(?:=(?:(?:[\w-]*)*|"(?:[^\r\n])*"))?)*\r\n/i', $encoded, $matches); if (!$is_chunked) { // Looks like it's not chunked after all return $data; } $length = hexdec(trim($matches[1])); if ($length === 0) { // Ignore trailer headers return $decoded; } $chunk_length = strlen($matches[0]); $decoded .= substr($encoded, $chunk_length, $length); $encoded = substr($encoded, $chunk_length + $length + 2); if (trim($encoded) === '0' || empty($encoded)) { return $decoded; } } // We'll never actually get down here // @codeCoverageIgnoreStart } // @codeCoverageIgnoreEnd /** * Convert a key => value array to a 'key: value' array for headers * * @param iterable $dictionary Dictionary of header values * @return array List of headers * * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not iterable. */ public static function flatten($dictionary) { if (InputValidator::is_iterable($dictionary) === false) { throw InvalidArgument::create(1, '$dictionary', 'iterable', gettype($dictionary)); } $return = []; foreach ($dictionary as $key => $value) { $return[] = sprintf('%s: %s', $key, $value); } return $return; } /** * Decompress an encoded body * * Implements gzip, compress and deflate. Guesses which it is by attempting * to decode. * * @param string $data Compressed data in one of the above formats * @return string Decompressed string * * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not a string. */ public static function decompress($data) { if (is_string($data) === false) { throw InvalidArgument::create(1, '$data', 'string', gettype($data)); } if (trim($data) === '') { // Empty body does not need further processing. return $data; } $marker = substr($data, 0, 2); if (!isset(self::$magic_compression_headers[$marker])) { // Not actually compressed. Probably cURL ruining this for us. return $data; } if (function_exists('gzdecode')) { $decoded = @gzdecode($data); if ($decoded !== false) { return $decoded; } } if (function_exists('gzinflate')) { $decoded = @gzinflate($data); if ($decoded !== false) { return $decoded; } } $decoded = self::compatible_gzinflate($data); if ($decoded !== false) { return $decoded; } if (function_exists('gzuncompress')) { $decoded = @gzuncompress($data); if ($decoded !== false) { return $decoded; } } return $data; } /** * Decompression of deflated string while staying compatible with the majority of servers. * * Certain Servers will return deflated data with headers which PHP's gzinflate() * function cannot handle out of the box. The following function has been created from * various snippets on the gzinflate() PHP documentation. * * Warning: Magic numbers within. Due to the potential different formats that the compressed * data may be returned in, some "magic offsets" are needed to ensure proper decompression * takes place. For a simple progmatic way to determine the magic offset in use, see: * https://core.trac.wordpress.org/ticket/18273 * * @since 1.6.0 * @link https://core.trac.wordpress.org/ticket/18273 * @link https://www.php.net/gzinflate#70875 * @link https://www.php.net/gzinflate#77336 * * @param string $gz_data String to decompress. * @return string|bool False on failure. * * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not a string. */ public static function compatible_gzinflate($gz_data) { if (is_string($gz_data) === false) { throw InvalidArgument::create(1, '$gz_data', 'string', gettype($gz_data)); } if (trim($gz_data) === '') { return false; } // Compressed data might contain a full zlib header, if so strip it for // gzinflate() if (substr($gz_data, 0, 3) === "\x1f\x8b\x08") { $i = 10; $flg = ord(substr($gz_data, 3, 1)); if ($flg > 0) { if ($flg & 4) { list($xlen) = unpack('v', substr($gz_data, $i, 2)); $i += 2 + $xlen; } if ($flg & 8) { $i = strpos($gz_data, "\0", $i) + 1; } if ($flg & 16) { $i = strpos($gz_data, "\0", $i) + 1; } if ($flg & 2) { $i += 2; } } $decompressed = self::compatible_gzinflate(substr($gz_data, $i)); if ($decompressed !== false) { return $decompressed; } } // If the data is Huffman Encoded, we must first strip the leading 2 // byte Huffman marker for gzinflate() // The response is Huffman coded by many compressors such as // java.util.zip.Deflater, Ruby's Zlib::Deflate, and .NET's // System.IO.Compression.DeflateStream. // // See https://decompres.blogspot.com/ for a quick explanation of this // data type $huffman_encoded = false; // low nibble of first byte should be 0x08 list(, $first_nibble) = unpack('h', $gz_data); // First 2 bytes should be divisible by 0x1F list(, $first_two_bytes) = unpack('n', $gz_data); if ($first_nibble === 0x08 && ($first_two_bytes % 0x1F) === 0) { $huffman_encoded = true; } if ($huffman_encoded) { $decompressed = @gzinflate(substr($gz_data, 2)); if ($decompressed !== false) { return $decompressed; } } if (substr($gz_data, 0, 4) === "\x50\x4b\x03\x04") { // ZIP file format header // Offset 6: 2 bytes, General-purpose field // Offset 26: 2 bytes, filename length // Offset 28: 2 bytes, optional field length // Offset 30: Filename field, followed by optional field, followed // immediately by data list(, $general_purpose_flag) = unpack('v', substr($gz_data, 6, 2)); // If the file has been compressed on the fly, 0x08 bit is set of // the general purpose field. We can use this to differentiate // between a compressed document, and a ZIP file $zip_compressed_on_the_fly = ((0x08 & $general_purpose_flag) === 0x08); if (!$zip_compressed_on_the_fly) { // Don't attempt to decode a compressed zip file return $gz_data; } // Determine the first byte of data, based on the above ZIP header // offsets: $first_file_start = array_sum(unpack('v2', substr($gz_data, 26, 4))); $decompressed = @gzinflate(substr($gz_data, 30 + $first_file_start)); if ($decompressed !== false) { return $decompressed; } return false; } // Finally fall back to straight gzinflate $decompressed = @gzinflate($gz_data); if ($decompressed !== false) { return $decompressed; } // Fallback for all above failing, not expected, but included for // debugging and preventing regressions and to track stats $decompressed = @gzinflate(substr($gz_data, 2)); if ($decompressed !== false) { return $decompressed; } return false; } } Auth/Basic.php000064400000004755152505214730007216 0ustar00user, $this->pass) = $args; return; } if ($args !== null) { throw InvalidArgument::create(1, '$args', 'array|null', gettype($args)); } } /** * Register the necessary callbacks * * @see \WpOrg\Requests\Auth\Basic::curl_before_send() * @see \WpOrg\Requests\Auth\Basic::fsockopen_header() * @param \WpOrg\Requests\Hooks $hooks Hook system */ public function register(Hooks $hooks) { $hooks->register('curl.before_send', [$this, 'curl_before_send']); $hooks->register('fsockopen.after_headers', [$this, 'fsockopen_header']); } /** * Set cURL parameters before the data is sent * * @param resource|\CurlHandle $handle cURL handle */ public function curl_before_send(&$handle) { curl_setopt($handle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); curl_setopt($handle, CURLOPT_USERPWD, $this->getAuthString()); } /** * Add extra headers to the request before sending * * @param string $out HTTP header string */ public function fsockopen_header(&$out) { $out .= sprintf("Authorization: Basic %s\r\n", base64_encode($this->getAuthString())); } /** * Get the authentication string (user:pass) * * @return string */ public function getAuthString() { return $this->user . ':' . $this->pass; } } Auth/error_log000064400000013243152505214730007371 0ustar00[19-Dec-2025 15:48:17 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Auth" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Auth/Basic.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Auth/Basic.php on line 23 [03-Apr-2026 19:13:59 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Auth" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Auth/Basic.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Auth/Basic.php on line 23 [05-Apr-2026 16:35:54 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Auth" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Auth/Basic.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Auth/Basic.php on line 23 [08-Apr-2026 14:32:20 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Auth" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Auth/Basic.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Auth/Basic.php on line 23 [15-Jul-2026 16:17:35 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Auth" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Auth/Basic.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Auth/Basic.php on line 23 [24-Jul-2026 12:13:20 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Auth" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Auth/Basic.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Auth/Basic.php on line 23 [25-Jul-2026 05:30:01 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Auth" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Auth/Basic.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Auth/Basic.php on line 23 [04-Aug-2026 23:28:59 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Auth" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Auth/Basic.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Auth/Basic.php on line 23 [13-Aug-2026 01:31:14 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Auth" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Auth/Basic.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Auth/Basic.php on line 23 [13-Aug-2026 13:02:54 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Auth" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Auth/Basic.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Auth/Basic.php on line 23 [19-Aug-2026 21:32:16 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Auth" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Auth/Basic.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Auth/Basic.php on line 23 [19-Aug-2026 22:01:07 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Auth" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Auth/Basic.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Auth/Basic.php on line 23 [19-Aug-2026 23:31:13 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Auth" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Auth/Basic.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Auth/Basic.php on line 23 [03-Sep-2026 06:33:22 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Auth" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Auth/Basic.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Auth/Basic.php on line 23 [03-Sep-2026 08:08:10 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Auth" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Auth/Basic.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Auth/Basic.php on line 23 [03-Sep-2026 12:20:48 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Auth" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Auth/Basic.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Auth/Basic.php on line 23 [04-Sep-2026 06:36:54 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Auth" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Auth/Basic.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Auth/Basic.php on line 23 [04-Sep-2026 17:49:17 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Auth" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Auth/Basic.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Auth/Basic.php on line 23 [08-Sep-2026 13:21:15 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Auth" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Auth/Basic.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Auth/Basic.php on line 23 Transport/Curl.php000064400000046646152505214730010202 0ustar00= 8.0. */ private $handle; /** * Hook dispatcher instance * * @var \WpOrg\Requests\Hooks */ private $hooks; /** * Have we finished the headers yet? * * @var boolean */ private $done_headers = false; /** * If streaming to a file, keep the file pointer * * @var resource */ private $stream_handle; /** * How many bytes are in the response body? * * @var int */ private $response_bytes; /** * What's the maximum number of bytes we should keep? * * @var int|bool Byte count, or false if no limit. */ private $response_byte_limit; /** * Constructor */ public function __construct() { $curl = curl_version(); $this->version = $curl['version_number']; $this->handle = curl_init(); curl_setopt($this->handle, CURLOPT_HEADER, false); curl_setopt($this->handle, CURLOPT_RETURNTRANSFER, 1); if ($this->version >= self::CURL_7_10_5) { curl_setopt($this->handle, CURLOPT_ENCODING, ''); } if (defined('CURLOPT_PROTOCOLS')) { // phpcs:ignore PHPCompatibility.Constants.NewConstants.curlopt_protocolsFound curl_setopt($this->handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); } if (defined('CURLOPT_REDIR_PROTOCOLS')) { // phpcs:ignore PHPCompatibility.Constants.NewConstants.curlopt_redir_protocolsFound curl_setopt($this->handle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); } } /** * Destructor */ public function __destruct() { if (is_resource($this->handle)) { // phpcs:ignore PHPCompatibility.FunctionUse.RemovedFunctions.curl_closeDeprecated,Generic.PHP.DeprecatedFunctions.Deprecated curl_close($this->handle); } } /** * Perform a request * * @param string|Stringable $url URL to request * @param array $headers Associative array of request headers * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD * @param array $options Request options, see {@see \WpOrg\Requests\Requests::response()} for documentation * @return string Raw HTTP result * * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $url argument is not a string or Stringable. * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $headers argument is not an array. * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $data parameter is not an array or string. * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array. * @throws \WpOrg\Requests\Exception On a cURL error (`curlerror`) */ public function request($url, $headers = [], $data = [], $options = []) { if (InputValidator::is_string_or_stringable($url) === false) { throw InvalidArgument::create(1, '$url', 'string|Stringable', gettype($url)); } if (is_array($headers) === false) { throw InvalidArgument::create(2, '$headers', 'array', gettype($headers)); } if (!is_array($data) && !is_string($data)) { if ($data === null) { $data = ''; } else { throw InvalidArgument::create(3, '$data', 'array|string', gettype($data)); } } if (is_array($options) === false) { throw InvalidArgument::create(4, '$options', 'array', gettype($options)); } $this->hooks = $options['hooks']; $this->setup_handle($url, $headers, $data, $options); $options['hooks']->dispatch('curl.before_send', [&$this->handle]); if ($options['filename'] !== false) { // phpcs:ignore WordPress.PHP.NoSilencedErrors -- Silenced the PHP native warning in favour of throwing an exception. $this->stream_handle = @fopen($options['filename'], 'wb'); if ($this->stream_handle === false) { $error = error_get_last(); throw new Exception($error['message'], 'fopen'); } } $this->response_data = ''; $this->response_bytes = 0; $this->response_byte_limit = false; if ($options['max_bytes'] !== false) { $this->response_byte_limit = $options['max_bytes']; } if (isset($options['verify'])) { if ($options['verify'] === false) { curl_setopt($this->handle, CURLOPT_SSL_VERIFYHOST, 0); curl_setopt($this->handle, CURLOPT_SSL_VERIFYPEER, 0); } elseif (is_string($options['verify'])) { curl_setopt($this->handle, CURLOPT_CAINFO, $options['verify']); } } if (isset($options['verifyname']) && $options['verifyname'] === false) { curl_setopt($this->handle, CURLOPT_SSL_VERIFYHOST, 0); } curl_exec($this->handle); $response = $this->response_data; $options['hooks']->dispatch('curl.after_send', []); if (curl_errno($this->handle) === CURLE_WRITE_ERROR || curl_errno($this->handle) === CURLE_BAD_CONTENT_ENCODING) { // Reset encoding and try again curl_setopt($this->handle, CURLOPT_ENCODING, 'none'); $this->response_data = ''; $this->response_bytes = 0; curl_exec($this->handle); $response = $this->response_data; } $this->process_response($response, $options); // Need to remove the $this reference from the curl handle. // Otherwise \WpOrg\Requests\Transport\Curl won't be garbage collected and the curl_close() will never be called. curl_setopt($this->handle, CURLOPT_HEADERFUNCTION, null); curl_setopt($this->handle, CURLOPT_WRITEFUNCTION, null); return $this->headers; } /** * Send multiple requests simultaneously * * @param array $requests Request data * @param array $options Global options * @return array Array of \WpOrg\Requests\Response objects (may contain \WpOrg\Requests\Exception or string responses as well) * * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $requests argument is not an array or iterable object with array access. * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array. */ public function request_multiple($requests, $options) { // If you're not requesting, we can't get any responses ¯\_(ツ)_/¯ if (empty($requests)) { return []; } if (InputValidator::has_array_access($requests) === false || InputValidator::is_iterable($requests) === false) { throw InvalidArgument::create(1, '$requests', 'array|ArrayAccess&Traversable', gettype($requests)); } if (is_array($options) === false) { throw InvalidArgument::create(2, '$options', 'array', gettype($options)); } $multihandle = curl_multi_init(); $subrequests = []; $subhandles = []; $class = get_class($this); foreach ($requests as $id => $request) { $subrequests[$id] = new $class(); $subhandles[$id] = $subrequests[$id]->get_subrequest_handle($request['url'], $request['headers'], $request['data'], $request['options']); $request['options']['hooks']->dispatch('curl.before_multi_add', [&$subhandles[$id]]); curl_multi_add_handle($multihandle, $subhandles[$id]); } $completed = 0; $responses = []; $subrequestcount = count($subrequests); $request['options']['hooks']->dispatch('curl.before_multi_exec', [&$multihandle]); do { $active = 0; do { $status = curl_multi_exec($multihandle, $active); } while ($status === CURLM_CALL_MULTI_PERFORM); $to_process = []; // Read the information as needed while ($done = curl_multi_info_read($multihandle)) { $key = array_search($done['handle'], $subhandles, true); if (!isset($to_process[$key])) { $to_process[$key] = $done; } } // Parse the finished requests before we start getting the new ones foreach ($to_process as $key => $done) { $options = $requests[$key]['options']; if ($done['result'] !== CURLE_OK) { //get error string for handle. $reason = curl_error($done['handle']); $exception = new CurlException( $reason, CurlException::EASY, $done['handle'], $done['result'] ); $responses[$key] = $exception; $options['hooks']->dispatch('transport.internal.parse_error', [&$responses[$key], $requests[$key]]); } else { $responses[$key] = $subrequests[$key]->process_response($subrequests[$key]->response_data, $options); $options['hooks']->dispatch('transport.internal.parse_response', [&$responses[$key], $requests[$key]]); } curl_multi_remove_handle($multihandle, $done['handle']); if (is_resource($done['handle'])) { // phpcs:ignore PHPCompatibility.FunctionUse.RemovedFunctions.curl_closeDeprecated,Generic.PHP.DeprecatedFunctions.Deprecated curl_close($done['handle']); } if (!is_string($responses[$key])) { $options['hooks']->dispatch('multiple.request.complete', [&$responses[$key], $key]); } $completed++; } } while ($active || $completed < $subrequestcount); $request['options']['hooks']->dispatch('curl.after_multi_exec', [&$multihandle]); curl_multi_close($multihandle); return $responses; } /** * Get the cURL handle for use in a multi-request * * @param string $url URL to request * @param array $headers Associative array of request headers * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD * @param array $options Request options, see {@see \WpOrg\Requests\Requests::response()} for documentation * @return resource|\CurlHandle Subrequest's cURL handle */ public function &get_subrequest_handle($url, $headers, $data, $options) { $this->setup_handle($url, $headers, $data, $options); if ($options['filename'] !== false) { $this->stream_handle = fopen($options['filename'], 'wb'); } $this->response_data = ''; $this->response_bytes = 0; $this->response_byte_limit = false; if ($options['max_bytes'] !== false) { $this->response_byte_limit = $options['max_bytes']; } $this->hooks = $options['hooks']; return $this->handle; } /** * Setup the cURL handle for the given data * * @param string $url URL to request * @param array $headers Associative array of request headers * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD * @param array $options Request options, see {@see \WpOrg\Requests\Requests::response()} for documentation */ private function setup_handle($url, $headers, $data, $options) { $options['hooks']->dispatch('curl.before_request', [&$this->handle]); // Force closing the connection for old versions of cURL (<7.22). if (!isset($headers['Connection'])) { $headers['Connection'] = 'close'; } /** * Add "Expect" header. * * By default, cURL adds a "Expect: 100-Continue" to most requests. This header can * add as much as a second to the time it takes for cURL to perform a request. To * prevent this, we need to set an empty "Expect" header. To match the behaviour of * Guzzle, we'll add the empty header to requests that are smaller than 1 MB and use * HTTP/1.1. * * https://curl.se/mail/lib-2017-07/0013.html */ if (!isset($headers['Expect']) && $options['protocol_version'] === 1.1) { $headers['Expect'] = $this->get_expect_header($data); } $headers = Requests::flatten($headers); if (!empty($data)) { $data_format = $options['data_format']; if ($data_format === 'query') { $url = self::format_get($url, $data); $data = ''; } elseif (!is_string($data)) { $data = http_build_query($data, '', '&'); } } switch ($options['type']) { case Requests::POST: curl_setopt($this->handle, CURLOPT_POST, true); curl_setopt($this->handle, CURLOPT_POSTFIELDS, $data); break; case Requests::HEAD: curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']); curl_setopt($this->handle, CURLOPT_NOBODY, true); break; case Requests::TRACE: curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']); break; case Requests::PATCH: case Requests::PUT: case Requests::DELETE: case Requests::OPTIONS: default: curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']); if (!empty($data)) { curl_setopt($this->handle, CURLOPT_POSTFIELDS, $data); } } // cURL requires a minimum timeout of 1 second when using the system // DNS resolver, as it uses `alarm()`, which is second resolution only. // There's no way to detect which DNS resolver is being used from our // end, so we need to round up regardless of the supplied timeout. // // https://github.com/curl/curl/blob/4f45240bc84a9aa648c8f7243be7b79e9f9323a5/lib/hostip.c#L606-L609 $timeout = max($options['timeout'], 1); if (is_int($timeout) || $this->version < self::CURL_7_16_2) { curl_setopt($this->handle, CURLOPT_TIMEOUT, ceil($timeout)); } else { // phpcs:ignore PHPCompatibility.Constants.NewConstants.curlopt_timeout_msFound curl_setopt($this->handle, CURLOPT_TIMEOUT_MS, round($timeout * 1000)); } if (is_int($options['connect_timeout']) || $this->version < self::CURL_7_16_2) { curl_setopt($this->handle, CURLOPT_CONNECTTIMEOUT, ceil($options['connect_timeout'])); } else { // phpcs:ignore PHPCompatibility.Constants.NewConstants.curlopt_connecttimeout_msFound curl_setopt($this->handle, CURLOPT_CONNECTTIMEOUT_MS, round($options['connect_timeout'] * 1000)); } curl_setopt($this->handle, CURLOPT_URL, $url); curl_setopt($this->handle, CURLOPT_USERAGENT, $options['useragent']); if (!empty($headers)) { curl_setopt($this->handle, CURLOPT_HTTPHEADER, $headers); } if ($options['protocol_version'] === 1.1) { curl_setopt($this->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); } else { curl_setopt($this->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); } if ($options['blocking'] === true) { curl_setopt($this->handle, CURLOPT_HEADERFUNCTION, [$this, 'stream_headers']); curl_setopt($this->handle, CURLOPT_WRITEFUNCTION, [$this, 'stream_body']); curl_setopt($this->handle, CURLOPT_BUFFERSIZE, Requests::BUFFER_SIZE); } } /** * Process a response * * @param string $response Response data from the body * @param array $options Request options * @return string|false HTTP response data including headers. False if non-blocking. * @throws \WpOrg\Requests\Exception If the request resulted in a cURL error. */ public function process_response($response, $options) { if ($options['blocking'] === false) { $fake_headers = ''; $options['hooks']->dispatch('curl.after_request', [&$fake_headers]); return false; } if ($options['filename'] !== false && $this->stream_handle) { fclose($this->stream_handle); $this->headers = trim($this->headers); } else { $this->headers .= $response; } if (curl_errno($this->handle)) { $error = sprintf( 'cURL error %s: %s', curl_errno($this->handle), curl_error($this->handle) ); throw new Exception($error, 'curlerror', $this->handle); } $this->info = curl_getinfo($this->handle); $options['hooks']->dispatch('curl.after_request', [&$this->headers, &$this->info]); return $this->headers; } /** * Collect the headers as they are received * * @param resource|\CurlHandle $handle cURL handle * @param string $headers Header string * @return integer Length of provided header */ public function stream_headers($handle, $headers) { // Why do we do this? cURL will send both the final response and any // interim responses, such as a 100 Continue. We don't need that. // (We may want to keep this somewhere just in case) if ($this->done_headers) { $this->headers = ''; $this->done_headers = false; } $this->headers .= $headers; if ($headers === "\r\n") { $this->done_headers = true; } return strlen($headers); } /** * Collect data as it's received * * @since 1.6.1 * * @param resource|\CurlHandle $handle cURL handle * @param string $data Body data * @return integer Length of provided data */ public function stream_body($handle, $data) { $this->hooks->dispatch('request.progress', [$data, $this->response_bytes, $this->response_byte_limit]); $data_length = strlen($data); // Are we limiting the response size? if ($this->response_byte_limit) { if ($this->response_bytes === $this->response_byte_limit) { // Already at maximum, move on return $data_length; } if (($this->response_bytes + $data_length) > $this->response_byte_limit) { // Limit the length $limited_length = ($this->response_byte_limit - $this->response_bytes); $data = substr($data, 0, $limited_length); } } if ($this->stream_handle) { fwrite($this->stream_handle, $data); } else { $this->response_data .= $data; } $this->response_bytes += strlen($data); return $data_length; } /** * Format a URL given GET data * * @param string $url Original URL. * @param array|object $data Data to build query using, see {@link https://www.php.net/http_build_query} * @return string URL with data */ private static function format_get($url, $data) { if (!empty($data)) { $query = ''; $url_parts = parse_url($url); if (empty($url_parts['query'])) { $url_parts['query'] = ''; } else { $query = $url_parts['query']; } $query .= '&' . http_build_query($data, '', '&'); $query = trim($query, '&'); if (empty($url_parts['query'])) { $url .= '?' . $query; } else { $url = str_replace($url_parts['query'], $query, $url); } } return $url; } /** * Self-test whether the transport can be used. * * The available capabilities to test for can be found in {@see \WpOrg\Requests\Capability}. * * @codeCoverageIgnore * @param array $capabilities Optional. Associative array of capabilities to test against, i.e. `['' => true]`. * @return bool Whether the transport can be used. */ public static function test($capabilities = []) { if (!function_exists('curl_init') || !function_exists('curl_exec')) { return false; } // If needed, check that our installed curl version supports SSL if (isset($capabilities[Capability::SSL]) && $capabilities[Capability::SSL]) { $curl_version = curl_version(); if (!(CURL_VERSION_SSL & $curl_version['features'])) { return false; } } return true; } /** * Get the correct "Expect" header for the given request data. * * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD. * @return string The "Expect" header. */ private function get_expect_header($data) { if (!is_array($data)) { return strlen((string) $data) >= 1048576 ? '100-Continue' : ''; } $bytesize = 0; $iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($data)); foreach ($iterator as $datum) { $bytesize += strlen((string) $datum); if ($bytesize >= 1048576) { return '100-Continue'; } } return ''; } } Transport/Fsockopen.php000064400000037317152505214730011217 0ustar00dispatch('fsockopen.before_request'); $url_parts = parse_url($url); if (empty($url_parts)) { throw new Exception('Invalid URL.', 'invalidurl', $url); } $host = $url_parts['host']; $context = stream_context_create(); $verifyname = false; $case_insensitive_headers = new CaseInsensitiveDictionary($headers); // HTTPS support if (isset($url_parts['scheme']) && strtolower($url_parts['scheme']) === 'https') { $remote_socket = 'ssl://' . $host; if (!isset($url_parts['port'])) { $url_parts['port'] = Port::HTTPS; } $context_options = [ 'verify_peer' => true, 'capture_peer_cert' => true, ]; $verifyname = true; // SNI, if enabled (OpenSSL >=0.9.8j) // phpcs:ignore PHPCompatibility.Constants.NewConstants.openssl_tlsext_server_nameFound if (defined('OPENSSL_TLSEXT_SERVER_NAME') && OPENSSL_TLSEXT_SERVER_NAME) { $context_options['SNI_enabled'] = true; if (isset($options['verifyname']) && $options['verifyname'] === false) { $context_options['SNI_enabled'] = false; } } if (isset($options['verify'])) { if ($options['verify'] === false) { $context_options['verify_peer'] = false; $context_options['verify_peer_name'] = false; $verifyname = false; } elseif (is_string($options['verify'])) { $context_options['cafile'] = $options['verify']; } } if (isset($options['verifyname']) && $options['verifyname'] === false) { $context_options['verify_peer_name'] = false; $verifyname = false; } // Handle the PHP 8.4 deprecation (PHP 9.0 removal) of the function signature we use for stream_context_set_option(). // Ref: https://wiki.php.net/rfc/deprecate_functions_with_overloaded_signatures#stream_context_set_option if (function_exists('stream_context_set_options')) { // PHP 8.3+. // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.stream_context_set_optionsFound stream_context_set_options($context, ['ssl' => $context_options]); } else { // PHP < 8.3. // phpcs:ignore PHPCompatibility.FunctionUse.OptionalToRequiredFunctionParameters stream_context_set_option($context, ['ssl' => $context_options]); } } else { $remote_socket = 'tcp://' . $host; } $this->max_bytes = $options['max_bytes']; if (!isset($url_parts['port'])) { $url_parts['port'] = Port::HTTP; } $remote_socket .= ':' . $url_parts['port']; // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_set_error_handler set_error_handler([$this, 'connect_error_handler'], E_WARNING | E_NOTICE); $options['hooks']->dispatch('fsockopen.remote_socket', [&$remote_socket]); $socket = stream_socket_client($remote_socket, $errno, $errstr, ceil($options['connect_timeout']), STREAM_CLIENT_CONNECT, $context); restore_error_handler(); if ($verifyname && !$this->verify_certificate_from_context($host, $context)) { throw new Exception('SSL certificate did not match the requested domain name', 'ssl.no_match'); } if (!$socket) { if ($errno === 0) { // Connection issue throw new Exception(rtrim($this->connect_error), 'fsockopen.connect_error'); } throw new Exception($errstr, 'fsockopenerror', null, $errno); } $data_format = $options['data_format']; if ($data_format === 'query') { $path = self::format_get($url_parts, $data); $data = ''; } else { $path = self::format_get($url_parts, []); } $options['hooks']->dispatch('fsockopen.remote_host_path', [&$path, $url]); $request_body = ''; $out = sprintf("%s %s HTTP/%.1F\r\n", $options['type'], $path, $options['protocol_version']); if ($options['type'] !== Requests::TRACE) { if (is_array($data)) { $request_body = http_build_query($data, '', '&'); } else { $request_body = $data; } // Always include Content-length on POST requests to prevent // 411 errors from some servers when the body is empty. if (!empty($data) || $options['type'] === Requests::POST) { if (!isset($case_insensitive_headers['Content-Length'])) { $headers['Content-Length'] = strlen($request_body); } if (!isset($case_insensitive_headers['Content-Type'])) { $headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'; } } } if (!isset($case_insensitive_headers['Host'])) { $out .= sprintf('Host: %s', $url_parts['host']); $scheme_lower = strtolower($url_parts['scheme']); if (($scheme_lower === 'http' && $url_parts['port'] !== Port::HTTP) || ($scheme_lower === 'https' && $url_parts['port'] !== Port::HTTPS)) { $out .= ':' . $url_parts['port']; } $out .= "\r\n"; } if (!isset($case_insensitive_headers['User-Agent'])) { $out .= sprintf("User-Agent: %s\r\n", $options['useragent']); } $accept_encoding = $this->accept_encoding(); if (!isset($case_insensitive_headers['Accept-Encoding']) && !empty($accept_encoding)) { $out .= sprintf("Accept-Encoding: %s\r\n", $accept_encoding); } $headers = Requests::flatten($headers); if (!empty($headers)) { $out .= implode("\r\n", $headers) . "\r\n"; } $options['hooks']->dispatch('fsockopen.after_headers', [&$out]); if (substr($out, -2) !== "\r\n") { $out .= "\r\n"; } if (!isset($case_insensitive_headers['Connection'])) { $out .= "Connection: Close\r\n"; } $out .= "\r\n" . $request_body; $options['hooks']->dispatch('fsockopen.before_send', [&$out]); fwrite($socket, $out); $options['hooks']->dispatch('fsockopen.after_send', [$out]); if (!$options['blocking']) { fclose($socket); $fake_headers = ''; $options['hooks']->dispatch('fsockopen.after_request', [&$fake_headers]); return ''; } $timeout_sec = (int) floor($options['timeout']); if ($timeout_sec === $options['timeout']) { $timeout_msec = 0; } else { $timeout_msec = self::SECOND_IN_MICROSECONDS * $options['timeout'] % self::SECOND_IN_MICROSECONDS; } stream_set_timeout($socket, $timeout_sec, $timeout_msec); $response = ''; $body = ''; $headers = ''; $this->info = stream_get_meta_data($socket); $size = 0; $doingbody = false; $download = false; if ($options['filename']) { // phpcs:ignore WordPress.PHP.NoSilencedErrors -- Silenced the PHP native warning in favour of throwing an exception. $download = @fopen($options['filename'], 'wb'); if ($download === false) { $error = error_get_last(); throw new Exception($error['message'], 'fopen'); } } while (!feof($socket)) { $this->info = stream_get_meta_data($socket); if ($this->info['timed_out']) { throw new Exception('fsocket timed out', 'timeout'); } $block = fread($socket, Requests::BUFFER_SIZE); if (!$doingbody) { $response .= $block; if (strpos($response, "\r\n\r\n")) { list($headers, $block) = explode("\r\n\r\n", $response, 2); $doingbody = true; } } // Are we in body mode now? if ($doingbody) { $options['hooks']->dispatch('request.progress', [$block, $size, $this->max_bytes]); $data_length = strlen($block); if ($this->max_bytes) { // Have we already hit a limit? if ($size === $this->max_bytes) { continue; } if (($size + $data_length) > $this->max_bytes) { // Limit the length $limited_length = ($this->max_bytes - $size); $block = substr($block, 0, $limited_length); } } $size += strlen($block); if ($download) { fwrite($download, $block); } else { $body .= $block; } } } $this->headers = $headers; if ($download) { fclose($download); } else { $this->headers .= "\r\n\r\n" . $body; } fclose($socket); $options['hooks']->dispatch('fsockopen.after_request', [&$this->headers, &$this->info]); return $this->headers; } /** * Send multiple requests simultaneously * * @param array $requests Request data (array of 'url', 'headers', 'data', 'options') as per {@see \WpOrg\Requests\Transport::request()} * @param array $options Global options, see {@see \WpOrg\Requests\Requests::response()} for documentation * @return array Array of \WpOrg\Requests\Response objects (may contain \WpOrg\Requests\Exception or string responses as well) * * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $requests argument is not an array or iterable object with array access. * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array. */ public function request_multiple($requests, $options) { // If you're not requesting, we can't get any responses ¯\_(ツ)_/¯ if (empty($requests)) { return []; } if (InputValidator::has_array_access($requests) === false || InputValidator::is_iterable($requests) === false) { throw InvalidArgument::create(1, '$requests', 'array|ArrayAccess&Traversable', gettype($requests)); } if (is_array($options) === false) { throw InvalidArgument::create(2, '$options', 'array', gettype($options)); } $responses = []; $class = get_class($this); foreach ($requests as $id => $request) { try { $handler = new $class(); $responses[$id] = $handler->request($request['url'], $request['headers'], $request['data'], $request['options']); $request['options']['hooks']->dispatch('transport.internal.parse_response', [&$responses[$id], $request]); } catch (Exception $e) { $responses[$id] = $e; } if (!is_string($responses[$id])) { $request['options']['hooks']->dispatch('multiple.request.complete', [&$responses[$id], $id]); } } return $responses; } /** * Retrieve the encodings we can accept * * @return string Accept-Encoding header value */ private static function accept_encoding() { $type = []; if (function_exists('gzinflate')) { $type[] = 'deflate;q=1.0'; } if (function_exists('gzuncompress')) { $type[] = 'compress;q=0.5'; } $type[] = 'gzip;q=0.5'; return implode(', ', $type); } /** * Format a URL given GET data * * @param array $url_parts Array of URL parts as received from {@link https://www.php.net/parse_url} * @param array|object $data Data to build query using, see {@link https://www.php.net/http_build_query} * @return string URL with data */ private static function format_get($url_parts, $data) { if (!empty($data)) { if (empty($url_parts['query'])) { $url_parts['query'] = ''; } $url_parts['query'] .= '&' . http_build_query($data, '', '&'); $url_parts['query'] = trim($url_parts['query'], '&'); } if (isset($url_parts['path'])) { if (isset($url_parts['query'])) { $get = $url_parts['path'] . '?' . $url_parts['query']; } else { $get = $url_parts['path']; } } else { $get = '/'; } return $get; } /** * Error handler for stream_socket_client() * * @param int $errno Error number (e.g. E_WARNING) * @param string $errstr Error message */ public function connect_error_handler($errno, $errstr) { // Double-check we can handle it if (($errno & E_WARNING) === 0 && ($errno & E_NOTICE) === 0) { // Return false to indicate the default error handler should engage return false; } $this->connect_error .= $errstr . "\n"; return true; } /** * Verify the certificate against common name and subject alternative names * * Unfortunately, PHP doesn't check the certificate against the alternative * names, leading things like 'https://www.github.com/' to be invalid. * Instead * * @link https://tools.ietf.org/html/rfc2818#section-3.1 RFC2818, Section 3.1 * * @param string $host Host name to verify against * @param resource $context Stream context * @return bool * * @throws \WpOrg\Requests\Exception On failure to connect via TLS (`fsockopen.ssl.connect_error`) * @throws \WpOrg\Requests\Exception On not obtaining a match for the host (`fsockopen.ssl.no_match`) */ public function verify_certificate_from_context($host, $context) { $meta = stream_context_get_options($context); // If we don't have SSL options, then we couldn't make the connection at // all if (empty($meta) || empty($meta['ssl']) || empty($meta['ssl']['peer_certificate'])) { throw new Exception(rtrim($this->connect_error), 'ssl.connect_error'); } $cert = openssl_x509_parse($meta['ssl']['peer_certificate']); return Ssl::verify_certificate($host, $cert); } /** * Self-test whether the transport can be used. * * The available capabilities to test for can be found in {@see \WpOrg\Requests\Capability}. * * @codeCoverageIgnore * @param array $capabilities Optional. Associative array of capabilities to test against, i.e. `['' => true]`. * @return bool Whether the transport can be used. */ public static function test($capabilities = []) { if (!function_exists('fsockopen')) { return false; } // If needed, check that streams support SSL if (isset($capabilities[Capability::SSL]) && $capabilities[Capability::SSL]) { if (!extension_loaded('openssl') || !function_exists('openssl_x509_parse')) { return false; } } return true; } } Transport/error_log000064400000027762152505214730010477 0ustar00[19-Dec-2025 15:48:34 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Curl.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Curl.php on line 25 [19-Dec-2025 15:48:34 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Fsockopen.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Fsockopen.php on line 25 [03-Apr-2026 19:13:58 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Curl.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Curl.php on line 25 [03-Apr-2026 19:13:58 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Fsockopen.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Fsockopen.php on line 25 [05-Apr-2026 16:35:53 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Fsockopen.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Fsockopen.php on line 25 [05-Apr-2026 16:35:53 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Curl.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Curl.php on line 25 [08-Apr-2026 14:32:11 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Curl.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Curl.php on line 25 [08-Apr-2026 14:32:11 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Fsockopen.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Fsockopen.php on line 25 [15-Jul-2026 16:17:37 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Fsockopen.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Fsockopen.php on line 25 [15-Jul-2026 16:17:38 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Curl.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Curl.php on line 25 [24-Jul-2026 12:13:19 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Curl.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Curl.php on line 25 [24-Jul-2026 12:13:20 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Fsockopen.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Fsockopen.php on line 25 [25-Jul-2026 05:30:01 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Curl.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Curl.php on line 25 [25-Jul-2026 05:30:02 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Fsockopen.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Fsockopen.php on line 25 [04-Aug-2026 23:29:16 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Curl.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Curl.php on line 25 [04-Aug-2026 23:29:16 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Fsockopen.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Fsockopen.php on line 25 [13-Aug-2026 01:31:16 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Curl.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Curl.php on line 25 [13-Aug-2026 01:31:16 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Fsockopen.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Fsockopen.php on line 25 [13-Aug-2026 13:02:53 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Fsockopen.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Fsockopen.php on line 25 [13-Aug-2026 13:02:53 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Curl.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Curl.php on line 25 [19-Aug-2026 21:32:16 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Fsockopen.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Fsockopen.php on line 25 [19-Aug-2026 21:32:16 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Curl.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Curl.php on line 25 [19-Aug-2026 22:01:09 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Curl.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Curl.php on line 25 [19-Aug-2026 22:01:09 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Fsockopen.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Fsockopen.php on line 25 [19-Aug-2026 23:31:15 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Fsockopen.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Fsockopen.php on line 25 [19-Aug-2026 23:31:15 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Curl.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Curl.php on line 25 [03-Sep-2026 06:32:47 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Fsockopen.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Fsockopen.php on line 25 [03-Sep-2026 06:46:33 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Curl.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Curl.php on line 25 [03-Sep-2026 08:08:12 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Curl.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Curl.php on line 25 [03-Sep-2026 08:08:12 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Fsockopen.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Fsockopen.php on line 25 [03-Sep-2026 14:10:49 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Fsockopen.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Fsockopen.php on line 25 [03-Sep-2026 14:11:11 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Curl.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Curl.php on line 25 [04-Sep-2026 05:26:58 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Fsockopen.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Fsockopen.php on line 25 [04-Sep-2026 07:21:44 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Curl.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Curl.php on line 25 [04-Sep-2026 22:07:50 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Fsockopen.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Fsockopen.php on line 25 [04-Sep-2026 22:08:00 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Curl.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Curl.php on line 25 [10-Sep-2026 02:36:50 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Fsockopen.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Fsockopen.php on line 25 [10-Sep-2026 04:07:17 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Curl.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Transport/Curl.php on line 25 HookManager.php000064400000001305152505214730007453 0ustar000 is executed later */ public function register($hook, $callback, $priority = 0); /** * Dispatch a message * * @param string $hook Hook name * @param array $parameters Parameters to pass to callbacks * @return boolean Successfulness */ public function dispatch($hook, $parameters = []); } Session.php000064400000021623152505214730006710 0ustar00useragent = 'X';` * * @var array */ public $options = []; /** * Create a new session * * @param string|Stringable|null $url Base URL for requests * @param array $headers Default headers for requests * @param array $data Default data for requests * @param array $options Default options for requests * * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $url argument is not a string, Stringable or null. * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $headers argument is not an array. * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $data argument is not an array. * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array. */ public function __construct($url = null, $headers = [], $data = [], $options = []) { if ($url !== null && InputValidator::is_string_or_stringable($url) === false) { throw InvalidArgument::create(1, '$url', 'string|Stringable|null', gettype($url)); } if (is_array($headers) === false) { throw InvalidArgument::create(2, '$headers', 'array', gettype($headers)); } if (is_array($data) === false) { throw InvalidArgument::create(3, '$data', 'array', gettype($data)); } if (is_array($options) === false) { throw InvalidArgument::create(4, '$options', 'array', gettype($options)); } $this->url = $url; $this->headers = $headers; $this->data = $data; $this->options = $options; if (empty($this->options['cookies'])) { $this->options['cookies'] = new Jar(); } } /** * Get a property's value * * @param string $name Property name. * @return mixed|null Property value, null if none found */ public function __get($name) { if (isset($this->options[$name])) { return $this->options[$name]; } return null; } /** * Set a property's value * * @param string $name Property name. * @param mixed $value Property value */ public function __set($name, $value) { $this->options[$name] = $value; } /** * Remove a property's value * * @param string $name Property name. */ public function __isset($name) { return isset($this->options[$name]); } /** * Remove a property's value * * @param string $name Property name. */ public function __unset($name) { unset($this->options[$name]); } /**#@+ * @see \WpOrg\Requests\Session::request() * @param string $url * @param array $headers * @param array $options * @return \WpOrg\Requests\Response */ /** * Send a GET request */ public function get($url, $headers = [], $options = []) { return $this->request($url, $headers, null, Requests::GET, $options); } /** * Send a HEAD request */ public function head($url, $headers = [], $options = []) { return $this->request($url, $headers, null, Requests::HEAD, $options); } /** * Send a DELETE request */ public function delete($url, $headers = [], $options = []) { return $this->request($url, $headers, null, Requests::DELETE, $options); } /**#@-*/ /**#@+ * @see \WpOrg\Requests\Session::request() * @param string $url * @param array $headers * @param array $data * @param array $options * @return \WpOrg\Requests\Response */ /** * Send a POST request */ public function post($url, $headers = [], $data = [], $options = []) { return $this->request($url, $headers, $data, Requests::POST, $options); } /** * Send a PUT request */ public function put($url, $headers = [], $data = [], $options = []) { return $this->request($url, $headers, $data, Requests::PUT, $options); } /** * Send a PATCH request * * Note: Unlike {@see \WpOrg\Requests\Session::post()} and {@see \WpOrg\Requests\Session::put()}, * `$headers` is required, as the specification recommends that should send an ETag * * @link https://tools.ietf.org/html/rfc5789 */ public function patch($url, $headers, $data = [], $options = []) { return $this->request($url, $headers, $data, Requests::PATCH, $options); } /**#@-*/ /** * Main interface for HTTP requests * * This method initiates a request and sends it via a transport before * parsing. * * @see \WpOrg\Requests\Requests::request() * * @param string $url URL to request * @param array $headers Extra headers to send with the request * @param array|null $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests * @param string $type HTTP request type (use \WpOrg\Requests\Requests constants) * @param array $options Options for the request (see {@see \WpOrg\Requests\Requests::request()}) * @return \WpOrg\Requests\Response * * @throws \WpOrg\Requests\Exception On invalid URLs (`nonhttp`) */ public function request($url, $headers = [], $data = [], $type = Requests::GET, $options = []) { $request = $this->merge_request(compact('url', 'headers', 'data', 'options')); return Requests::request($request['url'], $request['headers'], $request['data'], $type, $request['options']); } /** * Send multiple HTTP requests simultaneously * * @see \WpOrg\Requests\Requests::request_multiple() * * @param array $requests Requests data (see {@see \WpOrg\Requests\Requests::request_multiple()}) * @param array $options Global and default options (see {@see \WpOrg\Requests\Requests::request()}) * @return array Responses (either \WpOrg\Requests\Response or a \WpOrg\Requests\Exception object) * * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $requests argument is not an array or iterable object with array access. * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array. */ public function request_multiple($requests, $options = []) { if (InputValidator::has_array_access($requests) === false || InputValidator::is_iterable($requests) === false) { throw InvalidArgument::create(1, '$requests', 'array|ArrayAccess&Traversable', gettype($requests)); } if (is_array($options) === false) { throw InvalidArgument::create(2, '$options', 'array', gettype($options)); } foreach ($requests as $key => $request) { $requests[$key] = $this->merge_request($request, false); } $options = array_merge($this->options, $options); // Disallow forcing the type, as that's a per request setting unset($options['type']); return Requests::request_multiple($requests, $options); } public function __wakeup() { throw new \LogicException( __CLASS__ . ' should never be unserialized' ); } /** * Merge a request's data with the default data * * @param array $request Request data (same form as {@see \WpOrg\Requests\Session::request_multiple()}) * @param boolean $merge_options Should we merge options as well? * @return array Request data */ protected function merge_request($request, $merge_options = true) { if ($this->url !== null) { $request['url'] = Iri::absolutize($this->url, $request['url']); $request['url'] = $request['url']->uri; } if (empty($request['headers'])) { $request['headers'] = []; } $request['headers'] = array_merge($this->headers, $request['headers']); if (empty($request['data'])) { if (is_array($this->data)) { $request['data'] = $this->data; } } elseif (is_array($request['data']) && is_array($this->data)) { $request['data'] = array_merge($this->data, $request['data']); } if ($merge_options === true) { $request['options'] = array_merge($this->options, $request['options']); // Disallow forcing the type, as that's a per request setting unset($request['options']['type']); } return $request; } } error_log000064400000103310152505214730006463 0ustar00[19-Dec-2025 15:49:28 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/File.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/File.php on line 21 [19-Dec-2025 15:49:31 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Item.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Item.php on line 17 [19-Dec-2025 15:49:31 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Locator.php:26 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Locator.php on line 26 [19-Dec-2025 15:49:37 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Parser.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Parser.php on line 19 [19-Dec-2025 15:49:38 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Sanitize.php:34 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Sanitize.php on line 34 [19-Dec-2025 15:49:39 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Source.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Source.php on line 17 [03-Apr-2026 19:13:04 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/File.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/File.php on line 21 [03-Apr-2026 19:13:04 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Locator.php:26 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Locator.php on line 26 [03-Apr-2026 19:13:05 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Sanitize.php:34 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Sanitize.php on line 34 [03-Apr-2026 19:13:06 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Parser.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Parser.php on line 19 [03-Apr-2026 19:13:07 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Source.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Source.php on line 17 [03-Apr-2026 19:13:09 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Item.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Item.php on line 17 [05-Apr-2026 16:35:20 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Sanitize.php:34 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Sanitize.php on line 34 [05-Apr-2026 16:35:21 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Source.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Source.php on line 17 [05-Apr-2026 16:35:23 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Item.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Item.php on line 17 [05-Apr-2026 16:35:25 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Locator.php:26 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Locator.php on line 26 [05-Apr-2026 16:35:25 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/File.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/File.php on line 21 [05-Apr-2026 16:35:26 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Parser.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Parser.php on line 19 [08-Apr-2026 14:31:41 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Source.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Source.php on line 17 [08-Apr-2026 14:31:43 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Item.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Item.php on line 17 [08-Apr-2026 14:31:43 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/File.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/File.php on line 21 [08-Apr-2026 14:31:44 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Sanitize.php:34 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Sanitize.php on line 34 [08-Apr-2026 14:31:44 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Parser.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Parser.php on line 19 [08-Apr-2026 14:31:45 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Locator.php:26 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Locator.php on line 26 [15-Jul-2026 16:18:23 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Locator.php:26 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Locator.php on line 26 [15-Jul-2026 16:18:24 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Sanitize.php:34 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Sanitize.php on line 34 [15-Jul-2026 16:18:25 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Source.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Source.php on line 17 [15-Jul-2026 16:18:25 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Parser.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Parser.php on line 19 [15-Jul-2026 16:18:26 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/File.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/File.php on line 21 [15-Jul-2026 16:18:26 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Item.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Item.php on line 17 [24-Jul-2026 12:14:19 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Sanitize.php:34 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Sanitize.php on line 34 [24-Jul-2026 12:14:20 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Item.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Item.php on line 17 [24-Jul-2026 12:14:20 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Locator.php:26 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Locator.php on line 26 [24-Jul-2026 12:14:21 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Parser.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Parser.php on line 19 [24-Jul-2026 12:14:21 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Source.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Source.php on line 17 [24-Jul-2026 12:14:22 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/File.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/File.php on line 21 [25-Jul-2026 05:34:35 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Item.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Item.php on line 17 [25-Jul-2026 05:34:36 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Parser.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Parser.php on line 19 [25-Jul-2026 05:34:36 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Sanitize.php:34 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Sanitize.php on line 34 [25-Jul-2026 05:34:40 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/File.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/File.php on line 21 [25-Jul-2026 05:34:40 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Source.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Source.php on line 17 [25-Jul-2026 05:34:41 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Locator.php:26 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Locator.php on line 26 [04-Aug-2026 23:31:19 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Locator.php:26 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Locator.php on line 26 [04-Aug-2026 23:31:19 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Item.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Item.php on line 17 [04-Aug-2026 23:31:19 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Parser.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Parser.php on line 19 [04-Aug-2026 23:31:20 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Source.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Source.php on line 17 [04-Aug-2026 23:31:21 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Sanitize.php:34 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Sanitize.php on line 34 [04-Aug-2026 23:31:22 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/File.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/File.php on line 21 [13-Aug-2026 01:30:15 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/File.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/File.php on line 21 [13-Aug-2026 01:30:16 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Source.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Source.php on line 17 [13-Aug-2026 01:30:17 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Locator.php:26 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Locator.php on line 26 [13-Aug-2026 01:30:20 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Parser.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Parser.php on line 19 [13-Aug-2026 01:30:20 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Sanitize.php:34 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Sanitize.php on line 34 [13-Aug-2026 01:30:21 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Item.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Item.php on line 17 [13-Aug-2026 12:58:25 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Item.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Item.php on line 17 [13-Aug-2026 12:58:26 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/File.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/File.php on line 21 [13-Aug-2026 12:58:27 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Source.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Source.php on line 17 [13-Aug-2026 12:58:27 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Parser.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Parser.php on line 19 [13-Aug-2026 12:58:27 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Locator.php:26 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Locator.php on line 26 [13-Aug-2026 12:58:28 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Sanitize.php:34 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Sanitize.php on line 34 [19-Aug-2026 21:32:11 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Locator.php:26 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Locator.php on line 26 [19-Aug-2026 21:32:11 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/File.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/File.php on line 21 [19-Aug-2026 21:32:11 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Sanitize.php:34 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Sanitize.php on line 34 [19-Aug-2026 21:32:12 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Source.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Source.php on line 17 [19-Aug-2026 21:32:12 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Item.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Item.php on line 17 [19-Aug-2026 21:32:12 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Parser.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Parser.php on line 19 [19-Aug-2026 22:06:40 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Sanitize.php:34 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Sanitize.php on line 34 [19-Aug-2026 22:06:40 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Locator.php:26 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Locator.php on line 26 [19-Aug-2026 22:06:41 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/File.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/File.php on line 21 [19-Aug-2026 22:06:43 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Parser.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Parser.php on line 19 [19-Aug-2026 22:06:44 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Item.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Item.php on line 17 [19-Aug-2026 22:06:45 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Source.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Source.php on line 17 [19-Aug-2026 23:26:57 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/File.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/File.php on line 21 [19-Aug-2026 23:26:58 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Parser.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Parser.php on line 19 [19-Aug-2026 23:26:59 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Source.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Source.php on line 17 [19-Aug-2026 23:27:00 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Sanitize.php:34 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Sanitize.php on line 34 [19-Aug-2026 23:27:01 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Item.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Item.php on line 17 [19-Aug-2026 23:27:01 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Locator.php:26 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Locator.php on line 26 [03-Sep-2026 06:31:01 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Locator.php:26 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Locator.php on line 26 [03-Sep-2026 06:31:04 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Sanitize.php:34 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Sanitize.php on line 34 [03-Sep-2026 06:31:06 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Source.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Source.php on line 17 [03-Sep-2026 06:31:08 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Item.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Item.php on line 17 [03-Sep-2026 06:31:09 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Parser.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Parser.php on line 19 [03-Sep-2026 06:31:14 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/File.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/File.php on line 21 [03-Sep-2026 08:08:02 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/File.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/File.php on line 21 [03-Sep-2026 08:08:04 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Item.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Item.php on line 17 [03-Sep-2026 08:08:05 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Locator.php:26 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Locator.php on line 26 [03-Sep-2026 08:08:06 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Parser.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Parser.php on line 19 [03-Sep-2026 08:08:10 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Sanitize.php:34 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Sanitize.php on line 34 [03-Sep-2026 08:08:12 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Source.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Source.php on line 17 [03-Sep-2026 13:09:27 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/File.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/File.php on line 21 [03-Sep-2026 13:09:27 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Source.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Source.php on line 17 [03-Sep-2026 13:09:28 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Parser.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Parser.php on line 19 [03-Sep-2026 13:09:30 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Locator.php:26 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Locator.php on line 26 [03-Sep-2026 20:36:45 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Item.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Item.php on line 17 [04-Sep-2026 00:05:25 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/File.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/File.php on line 21 [04-Sep-2026 00:05:25 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Source.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Source.php on line 17 [04-Sep-2026 00:05:26 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Parser.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Parser.php on line 19 [04-Sep-2026 00:05:27 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Locator.php:26 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Locator.php on line 26 [04-Sep-2026 00:05:30 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Sanitize.php:34 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Sanitize.php on line 34 [04-Sep-2026 00:05:31 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Item.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Item.php on line 17 [04-Sep-2026 06:37:25 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Sanitize.php:34 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Sanitize.php on line 34 [04-Sep-2026 10:19:08 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Sanitize.php:34 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Sanitize.php on line 34 [04-Sep-2026 10:43:22 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/File.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/File.php on line 21 [04-Sep-2026 12:17:44 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Locator.php:26 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Locator.php on line 26 [04-Sep-2026 17:25:58 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Item.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Item.php on line 17 [04-Sep-2026 19:09:57 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Source.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Source.php on line 17 [04-Sep-2026 22:03:09 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Item.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Item.php on line 17 [04-Sep-2026 22:38:18 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/File.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/File.php on line 21 [04-Sep-2026 22:38:22 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Source.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Source.php on line 17 [04-Sep-2026 22:38:23 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Parser.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Parser.php on line 19 [04-Sep-2026 22:38:30 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Locator.php:26 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Locator.php on line 26 [05-Sep-2026 00:22:23 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Parser.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Parser.php on line 19 [05-Sep-2026 08:00:46 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\RegistryAware" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Sanitize.php:34 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Sanitize.php on line 34 Response.php000064400000010271152505214730007060 0ustar00headers = new Headers(); $this->cookies = new Jar(); } /** * Is the response a redirect? * * @return boolean True if redirect (3xx status), false if not. */ public function is_redirect() { $code = $this->status_code; return in_array($code, [300, 301, 302, 303, 307], true) || $code > 307 && $code < 400; } /** * Throws an exception if the request was not successful * * @param boolean $allow_redirects Set to false to throw on a 3xx as well * * @throws \WpOrg\Requests\Exception If `$allow_redirects` is false, and code is 3xx (`response.no_redirects`) * @throws \WpOrg\Requests\Exception\Http On non-successful status code. Exception class corresponds to "Status" + code (e.g. {@see \WpOrg\Requests\Exception\Http\Status404}) */ public function throw_for_status($allow_redirects = true) { if ($this->is_redirect()) { if ($allow_redirects !== true) { throw new Exception('Redirection not allowed', 'response.no_redirects', $this); } } elseif (!$this->success) { $exception = Http::get_class($this->status_code); throw new $exception(null, $this); } } /** * JSON decode the response body. * * The method parameters are the same as those for the PHP native `json_decode()` function. * * @link https://php.net/json-decode * * @param bool|null $associative Optional. When `true`, JSON objects will be returned as associative arrays; * When `false`, JSON objects will be returned as objects. * When `null`, JSON objects will be returned as associative arrays * or objects depending on whether `JSON_OBJECT_AS_ARRAY` is set in the flags. * Defaults to `true` (in contrast to the PHP native default of `null`). * @param int $depth Optional. Maximum nesting depth of the structure being decoded. * Defaults to `512`. * @param int $options Optional. Bitmask of JSON_BIGINT_AS_STRING, JSON_INVALID_UTF8_IGNORE, * JSON_INVALID_UTF8_SUBSTITUTE, JSON_OBJECT_AS_ARRAY, JSON_THROW_ON_ERROR. * Defaults to `0` (no options set). * * @return array * * @throws \WpOrg\Requests\Exception If `$this->body` is not valid json. */ public function decode_body($associative = true, $depth = 512, $options = 0) { $data = json_decode($this->body, $associative, $depth, $options); if (json_last_error() !== JSON_ERROR_NONE) { $last_error = json_last_error_msg(); throw new Exception('Unable to parse JSON data: ' . $last_error, 'response.invalid', $this); } return $data; } } Cookie.php000064400000036035152505214730006501 0ustar00name = $name; $this->value = $value; $this->attributes = $attributes; $default_flags = [ 'creation' => time(), 'last-access' => time(), 'persistent' => false, 'host-only' => true, ]; $this->flags = array_merge($default_flags, $flags); $this->reference_time = time(); if ($reference_time !== null) { $this->reference_time = $reference_time; } $this->normalize(); } /** * Get the cookie value * * Attributes and other data can be accessed via methods. */ public function __toString() { return $this->value; } /** * Check if a cookie is expired. * * Checks the age against $this->reference_time to determine if the cookie * is expired. * * @return boolean True if expired, false if time is valid. */ public function is_expired() { // RFC6265, s. 4.1.2.2: // If a cookie has both the Max-Age and the Expires attribute, the Max- // Age attribute has precedence and controls the expiration date of the // cookie. if (isset($this->attributes['max-age'])) { $max_age = $this->attributes['max-age']; return $max_age < $this->reference_time; } if (isset($this->attributes['expires'])) { $expires = $this->attributes['expires']; return $expires < $this->reference_time; } return false; } /** * Check if a cookie is valid for a given URI * * @param \WpOrg\Requests\Iri $uri URI to check * @return boolean Whether the cookie is valid for the given URI */ public function uri_matches(Iri $uri) { if (!$this->domain_matches($uri->host)) { return false; } if (!$this->path_matches($uri->path)) { return false; } return empty($this->attributes['secure']) || $uri->scheme === 'https'; } /** * Check if a cookie is valid for a given domain * * @param string $domain Domain to check * @return boolean Whether the cookie is valid for the given domain */ public function domain_matches($domain) { if (is_string($domain) === false) { return false; } if (!isset($this->attributes['domain'])) { // Cookies created manually; cookies created by Requests will set // the domain to the requested domain return true; } $cookie_domain = $this->attributes['domain']; if ($cookie_domain === $domain) { // The cookie domain and the passed domain are identical. return true; } // If the cookie is marked as host-only and we don't have an exact // match, reject the cookie if ($this->flags['host-only'] === true) { return false; } if (strlen($domain) <= strlen($cookie_domain)) { // For obvious reasons, the cookie domain cannot be a suffix if the passed domain // is shorter than the cookie domain return false; } if (substr($domain, -1 * strlen($cookie_domain)) !== $cookie_domain) { // The cookie domain should be a suffix of the passed domain. return false; } $prefix = substr($domain, 0, strlen($domain) - strlen($cookie_domain)); if (substr($prefix, -1) !== '.') { // The last character of the passed domain that is not included in the // domain string should be a %x2E (".") character. return false; } // The passed domain should be a host name (i.e., not an IP address). return !preg_match('#^(.+\.)\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$#', $domain); } /** * Check if a cookie is valid for a given path * * From the path-match check in RFC 6265 section 5.1.4 * * @param string $request_path Path to check * @return boolean Whether the cookie is valid for the given path */ public function path_matches($request_path) { if (empty($request_path)) { // Normalize empty path to root $request_path = '/'; } if (!isset($this->attributes['path'])) { // Cookies created manually; cookies created by Requests will set // the path to the requested path return true; } if (is_scalar($request_path) === false) { return false; } $cookie_path = $this->attributes['path']; if ($cookie_path === $request_path) { // The cookie-path and the request-path are identical. return true; } if (strlen($request_path) > strlen($cookie_path) && substr($request_path, 0, strlen($cookie_path)) === $cookie_path) { if (substr($cookie_path, -1) === '/') { // The cookie-path is a prefix of the request-path, and the last // character of the cookie-path is %x2F ("/"). return true; } if (substr($request_path, strlen($cookie_path), 1) === '/') { // The cookie-path is a prefix of the request-path, and the // first character of the request-path that is not included in // the cookie-path is a %x2F ("/") character. return true; } } return false; } /** * Normalize cookie and attributes * * @return boolean Whether the cookie was successfully normalized */ public function normalize() { foreach ($this->attributes as $key => $value) { $orig_value = $value; if (is_string($key)) { $value = $this->normalize_attribute($key, $value); } if ($value === null) { unset($this->attributes[$key]); continue; } if ($value !== $orig_value) { $this->attributes[$key] = $value; } } return true; } /** * Parse an individual cookie attribute * * Handles parsing individual attributes from the cookie values. * * @param string $name Attribute name * @param string|int|bool $value Attribute value (string/integer value, or true if empty/flag) * @return mixed Value if available, or null if the attribute value is invalid (and should be skipped) */ protected function normalize_attribute($name, $value) { switch (strtolower($name)) { case 'expires': // Expiration parsing, as per RFC 6265 section 5.2.1 if (is_int($value)) { return $value; } $expiry_time = strtotime($value); if ($expiry_time === false) { return null; } return $expiry_time; case 'max-age': // Expiration parsing, as per RFC 6265 section 5.2.2 if (is_int($value)) { return $value; } // Check that we have a valid age if (!preg_match('/^-?\d+$/', $value)) { return null; } $delta_seconds = (int) $value; if ($delta_seconds <= 0) { $expiry_time = 0; } else { $expiry_time = $this->reference_time + $delta_seconds; } return $expiry_time; case 'domain': // Domains are not required as per RFC 6265 section 5.2.3 if (empty($value)) { return null; } // Domain normalization, as per RFC 6265 section 5.2.3 if ($value[0] === '.') { $value = substr($value, 1); } return $value; default: return $value; } } /** * Format a cookie for a Cookie header * * This is used when sending cookies to a server. * * @return string Cookie formatted for Cookie header */ public function format_for_header() { return sprintf('%s=%s', $this->name, $this->value); } /** * Format a cookie for a Set-Cookie header * * This is used when sending cookies to clients. This isn't really * applicable to client-side usage, but might be handy for debugging. * * @return string Cookie formatted for Set-Cookie header */ public function format_for_set_cookie() { $header_value = $this->format_for_header(); if (!empty($this->attributes)) { $parts = []; foreach ($this->attributes as $key => $value) { // Ignore non-associative attributes if (is_numeric($key)) { $parts[] = $value; } else { $parts[] = sprintf('%s=%s', $key, $value); } } $header_value .= '; ' . implode('; ', $parts); } return $header_value; } /** * Parse a cookie string into a cookie object * * Based on Mozilla's parsing code in Firefox and related projects, which * is an intentional deviation from RFC 2109 and RFC 2616. RFC 6265 * specifies some of this handling, but not in a thorough manner. * * @param string $cookie_header Cookie header value (from a Set-Cookie header) * @param string $name * @param int|null $reference_time * @return \WpOrg\Requests\Cookie Parsed cookie object * * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $cookie_header argument is not a string. * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $name argument is not a string. */ public static function parse($cookie_header, $name = '', $reference_time = null) { if (is_string($cookie_header) === false) { throw InvalidArgument::create(1, '$cookie_header', 'string', gettype($cookie_header)); } if (is_string($name) === false) { throw InvalidArgument::create(2, '$name', 'string', gettype($name)); } $parts = explode(';', $cookie_header); $kvparts = array_shift($parts); if (!empty($name)) { $value = $cookie_header; } elseif (strpos($kvparts, '=') === false) { // Some sites might only have a value without the equals separator. // Deviate from RFC 6265 and pretend it was actually a blank name // (`=foo`) // // https://bugzilla.mozilla.org/show_bug.cgi?id=169091 $name = ''; $value = $kvparts; } else { list($name, $value) = explode('=', $kvparts, 2); } $name = trim($name); $value = trim($value); // Attribute keys are handled case-insensitively $attributes = new CaseInsensitiveDictionary(); if (!empty($parts)) { foreach ($parts as $part) { if (strpos($part, '=') === false) { $part_key = $part; $part_value = true; } else { list($part_key, $part_value) = explode('=', $part, 2); $part_value = trim($part_value); } $part_key = trim($part_key); $attributes[$part_key] = $part_value; } } return new static($name, $value, $attributes, [], $reference_time); } /** * Parse all Set-Cookie headers from request headers * * @param \WpOrg\Requests\Response\Headers $headers Headers to parse from * @param \WpOrg\Requests\Iri|null $origin URI for comparing cookie origins * @param int|null $time Reference time for expiration calculation * @return array * * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $origin argument is not null or an instance of the Iri class. */ public static function parse_from_headers(Headers $headers, $origin = null, $time = null) { $cookie_headers = $headers->getValues('Set-Cookie'); if (empty($cookie_headers)) { return []; } if ($origin !== null && !($origin instanceof Iri)) { throw InvalidArgument::create(2, '$origin', Iri::class . ' or null', gettype($origin)); } $cookies = []; foreach ($cookie_headers as $header) { $parsed = self::parse($header, '', $time); // Default domain/path attributes if (empty($parsed->attributes['domain']) && !empty($origin)) { $parsed->attributes['domain'] = $origin->host; $parsed->flags['host-only'] = true; } else { $parsed->flags['host-only'] = false; } $path_is_valid = (!empty($parsed->attributes['path']) && $parsed->attributes['path'][0] === '/'); if (!$path_is_valid && !empty($origin)) { $path = $origin->path; // Default path normalization as per RFC 6265 section 5.1.4 if (substr($path, 0, 1) !== '/') { // If the uri-path is empty or if the first character of // the uri-path is not a %x2F ("/") character, output // %x2F ("/") and skip the remaining steps. $path = '/'; } elseif (substr_count($path, '/') === 1) { // If the uri-path contains no more than one %x2F ("/") // character, output %x2F ("/") and skip the remaining // step. $path = '/'; } else { // Output the characters of the uri-path from the first // character up to, but not including, the right-most // %x2F ("/"). $path = substr($path, 0, strrpos($path, '/')); } $parsed->attributes['path'] = $path; } // Reject invalid cookie domains if (!empty($origin) && !$parsed->domain_matches($origin->host)) { continue; } $cookies[$parsed->name] = $parsed; } return $cookies; } } Utility/InputValidator.php000064400000004720152505214730011674 0ustar00 $value) { $this->offsetSet($offset, $value); } } /** * Check if the given item exists * * @param string $offset Item key * @return boolean Does the item exist? */ #[ReturnTypeWillChange] public function offsetExists($offset) { if (is_string($offset)) { $offset = strtolower($offset); } if ($offset === null) { $offset = ''; } return isset($this->data[$offset]); } /** * Get the value for the item * * @param string $offset Item key * @return string|null Item value (null if the item key doesn't exist) */ #[ReturnTypeWillChange] public function offsetGet($offset) { if (is_string($offset)) { $offset = strtolower($offset); } if ($offset === null) { $offset = ''; } if (!isset($this->data[$offset])) { return null; } return $this->data[$offset]; } /** * Set the given item * * @param string $offset Item name * @param string $value Item value * * @throws \WpOrg\Requests\Exception On attempting to use dictionary as list (`invalidset`) */ #[ReturnTypeWillChange] public function offsetSet($offset, $value) { if ($offset === null) { throw new Exception('Object is a dictionary, not a list', 'invalidset'); } if (is_string($offset)) { $offset = strtolower($offset); } $this->data[$offset] = $value; } /** * Unset the given header * * @param string $offset The key for the item to unset. */ #[ReturnTypeWillChange] public function offsetUnset($offset) { if (is_string($offset)) { $offset = strtolower($offset); } if ($offset === null) { $offset = ''; } unset($this->data[$offset]); } /** * Get an iterator for the data * * @return \ArrayIterator */ #[ReturnTypeWillChange] public function getIterator() { return new ArrayIterator($this->data); } /** * Get the headers as an array * * @return array Header data */ public function getAll() { return $this->data; } } Utility/FilteredIterator.php000064400000004200152505214730012170 0ustar00callback = $callback; } } /** * Prevent unserialization of the object for security reasons. * * @phpcs:disable PHPCompatibility.FunctionNameRestrictions.NewMagicMethods.__unserializeFound * * @param array $data Restored array of data originally serialized. * * @return void */ #[ReturnTypeWillChange] public function __unserialize($data) {} // phpcs:enable /** * Perform reinitialization tasks. * * Prevents a callback from being injected during unserialization of an object. * * @return void */ public function __wakeup() { unset($this->callback); } /** * Get the current item's value after filtering * * @return string */ #[ReturnTypeWillChange] public function current() { $value = parent::current(); if (is_callable($this->callback)) { $value = call_user_func($this->callback, $value); } return $value; } /** * Prevent creating a PHP value from a stored representation of the object for security reasons. * * @param string $data The serialized string. * * @return void */ #[ReturnTypeWillChange] public function unserialize($data) {} } Capability.php000064400000001214152505214730007340 0ustar00 0) { if ($position + $length > $strlen) { throw new Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character); } for ($position++; $remaining > 0; $position++) { $value = ord($input[$position]); // If it is invalid, count the sequence as invalid and reprocess the current byte: if (($value & 0xC0) !== 0x80) { throw new Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character); } --$remaining; $character |= ($value & 0x3F) << ($remaining * 6); } $position--; } if (// Non-shortest form sequences are invalid ($length > 1 && $character <= 0x7F) || ($length > 2 && $character <= 0x7FF) || ($length > 3 && $character <= 0xFFFF) // Outside of range of ucschar codepoints // Noncharacters || ($character & 0xFFFE) === 0xFFFE || ($character >= 0xFDD0 && $character <= 0xFDEF) || ( // Everything else not in ucschar ($character > 0xD7FF && $character < 0xF900) || $character < 0x20 || ($character > 0x7E && $character < 0xA0) || $character > 0xEFFFD ) ) { throw new Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character); } $codepoints[] = $character; } return $codepoints; } /** * RFC3492-compliant encoder * * @internal Pseudo-code from Section 6.3 is commented with "#" next to relevant code * * @param string $input UTF-8 encoded string to encode * @return string Punycode-encoded string * * @throws \WpOrg\Requests\Exception On character outside of the domain (never happens with Punycode) (`idna.character_outside_domain`) */ public static function punycode_encode($input) { $output = ''; // let n = initial_n $n = self::BOOTSTRAP_INITIAL_N; // let delta = 0 $delta = 0; // let bias = initial_bias $bias = self::BOOTSTRAP_INITIAL_BIAS; // let h = b = the number of basic code points in the input $h = 0; $b = 0; // see loop // copy them to the output in order $codepoints = self::utf8_to_codepoints($input); $extended = []; foreach ($codepoints as $char) { if ($char < 128) { // Character is valid ASCII // TODO: this should also check if it's valid for a URL $output .= chr($char); $h++; // Check if the character is non-ASCII, but below initial n // This never occurs for Punycode, so ignore in coverage // @codeCoverageIgnoreStart } elseif ($char < $n) { throw new Exception('Invalid character', 'idna.character_outside_domain', $char); // @codeCoverageIgnoreEnd } else { $extended[$char] = true; } } $extended = array_keys($extended); sort($extended); $b = $h; // [copy them] followed by a delimiter if b > 0 if (strlen($output) > 0) { $output .= '-'; } // {if the input contains a non-basic code point < n then fail} // while h < length(input) do begin $codepointcount = count($codepoints); while ($h < $codepointcount) { // let m = the minimum code point >= n in the input $m = array_shift($extended); //printf('next code point to insert is %s' . PHP_EOL, dechex($m)); // let delta = delta + (m - n) * (h + 1), fail on overflow $delta += ($m - $n) * ($h + 1); // let n = m $n = $m; // for each code point c in the input (in order) do begin for ($num = 0; $num < $codepointcount; $num++) { $c = $codepoints[$num]; // if c < n then increment delta, fail on overflow if ($c < $n) { $delta++; } elseif ($c === $n) { // if c == n then begin // let q = delta $q = $delta; // for k = base to infinity in steps of base do begin for ($k = self::BOOTSTRAP_BASE; ; $k += self::BOOTSTRAP_BASE) { // let t = tmin if k <= bias {+ tmin}, or // tmax if k >= bias + tmax, or k - bias otherwise if ($k <= ($bias + self::BOOTSTRAP_TMIN)) { $t = self::BOOTSTRAP_TMIN; } elseif ($k >= ($bias + self::BOOTSTRAP_TMAX)) { $t = self::BOOTSTRAP_TMAX; } else { $t = $k - $bias; } // if q < t then break if ($q < $t) { break; } // output the code point for digit t + ((q - t) mod (base - t)) $digit = (int) ($t + (($q - $t) % (self::BOOTSTRAP_BASE - $t))); $output .= self::digit_to_char($digit); // let q = (q - t) div (base - t) $q = (int) floor(($q - $t) / (self::BOOTSTRAP_BASE - $t)); } // end // output the code point for digit q $output .= self::digit_to_char($q); // let bias = adapt(delta, h + 1, test h equals b?) $bias = self::adapt($delta, $h + 1, $h === $b); // let delta = 0 $delta = 0; // increment h $h++; } // end } // end // increment delta and n $delta++; $n++; } // end return $output; } /** * Convert a digit to its respective character * * @link https://tools.ietf.org/html/rfc3492#section-5 * * @param int $digit Digit in the range 0-35 * @return string Single character corresponding to digit * * @throws \WpOrg\Requests\Exception On invalid digit (`idna.invalid_digit`) */ protected static function digit_to_char($digit) { // @codeCoverageIgnoreStart // As far as I know, this never happens, but still good to be sure. if ($digit < 0 || $digit > 35) { throw new Exception(sprintf('Invalid digit %d', $digit), 'idna.invalid_digit', $digit); } // @codeCoverageIgnoreEnd $digits = 'abcdefghijklmnopqrstuvwxyz0123456789'; return substr($digits, $digit, 1); } /** * Adapt the bias * * @link https://tools.ietf.org/html/rfc3492#section-6.1 * @param int $delta * @param int $numpoints * @param bool $firsttime * @return int|float New bias * * function adapt(delta,numpoints,firsttime): */ protected static function adapt($delta, $numpoints, $firsttime) { // if firsttime then let delta = delta div damp if ($firsttime) { $delta = floor($delta / self::BOOTSTRAP_DAMP); } else { // else let delta = delta div 2 $delta = floor($delta / 2); } // let delta = delta + (delta div numpoints) $delta += floor($delta / $numpoints); // let k = 0 $k = 0; // while delta > ((base - tmin) * tmax) div 2 do begin $max = floor(((self::BOOTSTRAP_BASE - self::BOOTSTRAP_TMIN) * self::BOOTSTRAP_TMAX) / 2); while ($delta > $max) { // let delta = delta div (base - tmin) $delta = floor($delta / (self::BOOTSTRAP_BASE - self::BOOTSTRAP_TMIN)); // let k = k + base $k += self::BOOTSTRAP_BASE; } // end // return k + (((base - tmin + 1) * delta) div (delta + skew)) return $k + floor(((self::BOOTSTRAP_BASE - self::BOOTSTRAP_TMIN + 1) * $delta) / ($delta + self::BOOTSTRAP_SKEW)); } } Cookie/Jar.php000064400000010413152505214730007205 0ustar00cookies = $cookies; } /** * Normalise cookie data into a \WpOrg\Requests\Cookie * * @param string|\WpOrg\Requests\Cookie $cookie Cookie header value, possibly pre-parsed (object). * @param string $key Optional. The name for this cookie. * @return \WpOrg\Requests\Cookie */ public function normalize_cookie($cookie, $key = '') { if ($cookie instanceof Cookie) { return $cookie; } return Cookie::parse($cookie, $key); } /** * Check if the given item exists * * @param string $offset Item key * @return boolean Does the item exist? */ #[ReturnTypeWillChange] public function offsetExists($offset) { return isset($this->cookies[$offset]); } /** * Get the value for the item * * @param string $offset Item key * @return string|null Item value (null if offsetExists is false) */ #[ReturnTypeWillChange] public function offsetGet($offset) { if (!isset($this->cookies[$offset])) { return null; } return $this->cookies[$offset]; } /** * Set the given item * * @param string $offset Item name * @param string $value Item value * * @throws \WpOrg\Requests\Exception On attempting to use dictionary as list (`invalidset`) */ #[ReturnTypeWillChange] public function offsetSet($offset, $value) { if ($offset === null) { throw new Exception('Object is a dictionary, not a list', 'invalidset'); } $this->cookies[$offset] = $value; } /** * Unset the given header * * @param string $offset The key for the item to unset. */ #[ReturnTypeWillChange] public function offsetUnset($offset) { unset($this->cookies[$offset]); } /** * Get an iterator for the data * * @return \ArrayIterator */ #[ReturnTypeWillChange] public function getIterator() { return new ArrayIterator($this->cookies); } /** * Register the cookie handler with the request's hooking system * * @param \WpOrg\Requests\HookManager $hooks Hooking system */ public function register(HookManager $hooks) { $hooks->register('requests.before_request', [$this, 'before_request']); $hooks->register('requests.before_redirect_check', [$this, 'before_redirect_check']); } /** * Add Cookie header to a request if we have any * * As per RFC 6265, cookies are separated by '; ' * * @param string $url * @param array $headers * @param array $data * @param string $type * @param array $options */ public function before_request($url, &$headers, &$data, &$type, &$options) { if (!$url instanceof Iri) { $url = new Iri($url); } if (!empty($this->cookies)) { $cookies = []; foreach ($this->cookies as $key => $cookie) { $cookie = $this->normalize_cookie($cookie, $key); // Skip expired cookies if ($cookie->is_expired()) { continue; } if ($cookie->domain_matches($url->host)) { $cookies[] = $cookie->format_for_header(); } } $headers['Cookie'] = implode('; ', $cookies); } } /** * Parse all cookies from a response and attach them to the response * * @param \WpOrg\Requests\Response $response Response as received. */ public function before_redirect_check(Response $response) { $url = $response->url; if (!$url instanceof Iri) { $url = new Iri($url); } $cookies = Cookie::parse_from_headers($response->headers, $url); $this->cookies = array_merge($this->cookies, $cookies); $response->cookies = $this; } } Exception/ArgumentCount.php000064400000002664152505214730012022 0ustar00type = $type; } if ($code !== null) { $this->code = (int) $code; } if ($message !== null) { $this->reason = $message; } $message = sprintf('%d %s', $this->code, $this->reason); parent::__construct($message, $this->type, $data, $this->code); } /** * Get the error message. * * @return string */ public function getReason() { return $this->reason; } } Exception/Transport/error_log000064400000014060152505214730012420 0ustar00[03-Apr-2026 19:13:47 UTC] PHP Fatal error: Uncaught Error: Class "WpOrg\Requests\Exception\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Exception/Transport/Curl.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Exception/Transport/Curl.php on line 17 [05-Apr-2026 16:35:58 UTC] PHP Fatal error: Uncaught Error: Class "WpOrg\Requests\Exception\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Exception/Transport/Curl.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Exception/Transport/Curl.php on line 17 [08-Apr-2026 14:32:13 UTC] PHP Fatal error: Uncaught Error: Class "WpOrg\Requests\Exception\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Exception/Transport/Curl.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Exception/Transport/Curl.php on line 17 [15-Jul-2026 16:17:27 UTC] PHP Fatal error: Uncaught Error: Class "WpOrg\Requests\Exception\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Exception/Transport/Curl.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Exception/Transport/Curl.php on line 17 [24-Jul-2026 12:13:12 UTC] PHP Fatal error: Uncaught Error: Class "WpOrg\Requests\Exception\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Exception/Transport/Curl.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Exception/Transport/Curl.php on line 17 [25-Jul-2026 05:30:05 UTC] PHP Fatal error: Uncaught Error: Class "WpOrg\Requests\Exception\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Exception/Transport/Curl.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Exception/Transport/Curl.php on line 17 [04-Aug-2026 23:29:15 UTC] PHP Fatal error: Uncaught Error: Class "WpOrg\Requests\Exception\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Exception/Transport/Curl.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Exception/Transport/Curl.php on line 17 [13-Aug-2026 01:31:28 UTC] PHP Fatal error: Uncaught Error: Class "WpOrg\Requests\Exception\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Exception/Transport/Curl.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Exception/Transport/Curl.php on line 17 [13-Aug-2026 13:02:49 UTC] PHP Fatal error: Uncaught Error: Class "WpOrg\Requests\Exception\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Exception/Transport/Curl.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Exception/Transport/Curl.php on line 17 [19-Aug-2026 21:32:17 UTC] PHP Fatal error: Uncaught Error: Class "WpOrg\Requests\Exception\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Exception/Transport/Curl.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Exception/Transport/Curl.php on line 17 [19-Aug-2026 22:00:48 UTC] PHP Fatal error: Uncaught Error: Class "WpOrg\Requests\Exception\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Exception/Transport/Curl.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Exception/Transport/Curl.php on line 17 [19-Aug-2026 23:31:06 UTC] PHP Fatal error: Uncaught Error: Class "WpOrg\Requests\Exception\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Exception/Transport/Curl.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Exception/Transport/Curl.php on line 17 [03-Sep-2026 06:42:49 UTC] PHP Fatal error: Uncaught Error: Class "WpOrg\Requests\Exception\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Exception/Transport/Curl.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Exception/Transport/Curl.php on line 17 [03-Sep-2026 08:08:19 UTC] PHP Fatal error: Uncaught Error: Class "WpOrg\Requests\Exception\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Exception/Transport/Curl.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Exception/Transport/Curl.php on line 17 [03-Sep-2026 14:08:46 UTC] PHP Fatal error: Uncaught Error: Class "WpOrg\Requests\Exception\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Exception/Transport/Curl.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Exception/Transport/Curl.php on line 17 [04-Sep-2026 22:07:55 UTC] PHP Fatal error: Uncaught Error: Class "WpOrg\Requests\Exception\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Exception/Transport/Curl.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Exception/Transport/Curl.php on line 17 [06-Sep-2026 09:16:13 UTC] PHP Fatal error: Uncaught Error: Class "WpOrg\Requests\Exception\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Exception/Transport/Curl.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Exception/Transport/Curl.php on line 17 [08-Sep-2026 16:23:00 UTC] PHP Fatal error: Uncaught Error: Class "WpOrg\Requests\Exception\Transport" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Exception/Transport/Curl.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Exception/Transport/Curl.php on line 17 Exception/Http/Status416.php000064400000001005152505214730011650 0ustar00code = (int) $data->status_code; } parent::__construct($reason, $data); } } Exception/Http/Status429.php000064400000001163152505214730011661 0ustar00reason = $reason; } $message = sprintf('%d %s', $this->code, $this->reason); parent::__construct($message, 'httpresponse', $data, $this->code); } /** * Get the status message. * * @return string */ public function getReason() { return $this->reason; } /** * Get the correct exception class for a given error code * * @param int|bool $code HTTP status code, or false if unavailable * @return string Exception class name to use */ public static function get_class($code) { if (!$code) { return StatusUnknown::class; } $class = sprintf('\WpOrg\Requests\Exception\Http\Status%d', $code); if (class_exists($class)) { return $class; } return StatusUnknown::class; } } Exception/Transport.php000064400000000364152505214730011216 0ustar00 $capabilities Optional. Associative array of capabilities to test against, i.e. `['' => true]`. * @return bool Whether the transport can be used. */ public static function test($capabilities = []); } Proxy/error_log000064400000013266152505214730007616 0ustar00[19-Dec-2025 15:48:28 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Proxy" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Proxy/Http.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Proxy/Http.php on line 24 [03-Apr-2026 19:13:45 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Proxy" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Proxy/Http.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Proxy/Http.php on line 24 [05-Apr-2026 16:35:55 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Proxy" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Proxy/Http.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Proxy/Http.php on line 24 [08-Apr-2026 14:32:11 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Proxy" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Proxy/Http.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Proxy/Http.php on line 24 [15-Jul-2026 16:17:36 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Proxy" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Proxy/Http.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Proxy/Http.php on line 24 [24-Jul-2026 12:13:19 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Proxy" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Proxy/Http.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Proxy/Http.php on line 24 [25-Jul-2026 05:30:02 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Proxy" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Proxy/Http.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Proxy/Http.php on line 24 [04-Aug-2026 23:29:15 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Proxy" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Proxy/Http.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Proxy/Http.php on line 24 [13-Aug-2026 01:31:12 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Proxy" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Proxy/Http.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Proxy/Http.php on line 24 [13-Aug-2026 13:02:38 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Proxy" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Proxy/Http.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Proxy/Http.php on line 24 [19-Aug-2026 21:32:17 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Proxy" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Proxy/Http.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Proxy/Http.php on line 24 [19-Aug-2026 22:01:13 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Proxy" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Proxy/Http.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Proxy/Http.php on line 24 [19-Aug-2026 23:31:16 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Proxy" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Proxy/Http.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Proxy/Http.php on line 24 [03-Sep-2026 06:33:22 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Proxy" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Proxy/Http.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Proxy/Http.php on line 24 [03-Sep-2026 08:08:11 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Proxy" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Proxy/Http.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Proxy/Http.php on line 24 [03-Sep-2026 13:09:27 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Proxy" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Proxy/Http.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Proxy/Http.php on line 24 [04-Sep-2026 17:06:26 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Proxy" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Proxy/Http.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Proxy/Http.php on line 24 [04-Sep-2026 22:38:21 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Proxy" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Proxy/Http.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Proxy/Http.php on line 24 [08-Sep-2026 10:54:56 UTC] PHP Fatal error: Uncaught Error: Interface "WpOrg\Requests\Proxy" not found in /home/veronikagstoette/public_html/wp-includes/Requests/src/Proxy/Http.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/Requests/src/Proxy/Http.php on line 24 Proxy/Http.php000064400000010171152505214730007321 0ustar00proxy = $args; } elseif (is_array($args)) { if (count($args) === 1) { list($this->proxy) = $args; } elseif (count($args) === 3) { list($this->proxy, $this->user, $this->pass) = $args; $this->use_authentication = true; } else { throw ArgumentCount::create( 'an array with exactly one element or exactly three elements', count($args), 'proxyhttpbadargs' ); } } elseif ($args !== null) { throw InvalidArgument::create(1, '$args', 'array|string|null', gettype($args)); } } /** * Register the necessary callbacks * * @since 1.6 * @see \WpOrg\Requests\Proxy\Http::curl_before_send() * @see \WpOrg\Requests\Proxy\Http::fsockopen_remote_socket() * @see \WpOrg\Requests\Proxy\Http::fsockopen_remote_host_path() * @see \WpOrg\Requests\Proxy\Http::fsockopen_header() * @param \WpOrg\Requests\Hooks $hooks Hook system */ public function register(Hooks $hooks) { $hooks->register('curl.before_send', [$this, 'curl_before_send']); $hooks->register('fsockopen.remote_socket', [$this, 'fsockopen_remote_socket']); $hooks->register('fsockopen.remote_host_path', [$this, 'fsockopen_remote_host_path']); if ($this->use_authentication) { $hooks->register('fsockopen.after_headers', [$this, 'fsockopen_header']); } } /** * Set cURL parameters before the data is sent * * @since 1.6 * @param resource|\CurlHandle $handle cURL handle */ public function curl_before_send(&$handle) { curl_setopt($handle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); curl_setopt($handle, CURLOPT_PROXY, $this->proxy); if ($this->use_authentication) { curl_setopt($handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY); curl_setopt($handle, CURLOPT_PROXYUSERPWD, $this->get_auth_string()); } } /** * Alter remote socket information before opening socket connection * * @since 1.6 * @param string $remote_socket Socket connection string */ public function fsockopen_remote_socket(&$remote_socket) { $remote_socket = $this->proxy; } /** * Alter remote path before getting stream data * * @since 1.6 * @param string $path Path to send in HTTP request string ("GET ...") * @param string $url Full URL we're requesting */ public function fsockopen_remote_host_path(&$path, $url) { $path = $url; } /** * Add extra headers to the request before sending * * @since 1.6 * @param string $out HTTP header string */ public function fsockopen_header(&$out) { $out .= sprintf("Proxy-Authorization: Basic %s\r\n", base64_encode($this->get_auth_string())); } /** * Get the authentication string (user:pass) * * @since 1.6 * @return string */ public function get_auth_string() { return $this->user . ':' . $this->pass; } } Auth.php000064400000001534152505214730006165 0ustar00 0) { // Whitespace detected. This can never be a dNSName. return false; } $parts = explode('.', $reference); if ($parts !== array_filter($parts)) { // DNSName cannot contain two dots next to each other. return false; } // Check the first part of the name $first = array_shift($parts); if (strpos($first, '*') !== false) { // Check that the wildcard is the full part if ($first !== '*') { return false; } // Check that we have at least 3 components (including first) if (count($parts) < 2) { return false; } } // Check the remaining parts foreach ($parts as $part) { if (strpos($part, '*') !== false) { return false; } } // Nothing found, verified! return true; } /** * Match a hostname against a dNSName reference * * @param string|Stringable $host Requested host * @param string|Stringable $reference dNSName to match against * @return boolean Does the domain match? * @throws \WpOrg\Requests\Exception\InvalidArgument When either of the passed arguments is not a string or a stringable object. */ public static function match_domain($host, $reference) { if (InputValidator::is_string_or_stringable($host) === false) { throw InvalidArgument::create(1, '$host', 'string|Stringable', gettype($host)); } // Check if the reference is blocklisted first if (self::verify_reference_name($reference) !== true) { return false; } // Check for a direct match if ((string) $host === (string) $reference) { return true; } // Calculate the valid wildcard match if the host is not an IP address // Also validates that the host has 3 parts or more, as per Firefox's ruleset, // as a wildcard reference is only allowed with 3 parts or more, so the // comparison will never match if host doesn't contain 3 parts or more as well. if (ip2long($host) === false) { $parts = explode('.', $host); $parts[0] = '*'; $wildcard = implode('.', $parts); if ($wildcard === (string) $reference) { return true; } } return false; } } Ipv6.php000064400000013013152505214730006103 0ustar00 FF01:0:0:0:0:0:0:101 * ::1 -> 0:0:0:0:0:0:0:1 * * @author Alexander Merz * @author elfrink at introweb dot nl * @author Josh Peck * @copyright 2003-2005 The PHP Group * @license https://opensource.org/licenses/bsd-license.php * * @param string|Stringable $ip An IPv6 address * @return string The uncompressed IPv6 address * * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not a string or a stringable object. */ public static function uncompress($ip) { if (InputValidator::is_string_or_stringable($ip) === false) { throw InvalidArgument::create(1, '$ip', 'string|Stringable', gettype($ip)); } $ip = (string) $ip; if (substr_count($ip, '::') !== 1) { return $ip; } list($ip1, $ip2) = explode('::', $ip); $c1 = ($ip1 === '') ? -1 : substr_count($ip1, ':'); $c2 = ($ip2 === '') ? -1 : substr_count($ip2, ':'); if (strpos($ip2, '.') !== false) { $c2++; } if ($c1 === -1 && $c2 === -1) { // :: $ip = '0:0:0:0:0:0:0:0'; } elseif ($c1 === -1) { // ::xxx $fill = str_repeat('0:', 7 - $c2); $ip = str_replace('::', $fill, $ip); } elseif ($c2 === -1) { // xxx:: $fill = str_repeat(':0', 7 - $c1); $ip = str_replace('::', $fill, $ip); } else { // xxx::xxx $fill = ':' . str_repeat('0:', 6 - $c2 - $c1); $ip = str_replace('::', $fill, $ip); } return $ip; } /** * Compresses an IPv6 address * * RFC 4291 allows you to compress consecutive zero pieces in an address to * '::'. This method expects a valid IPv6 address and compresses consecutive * zero pieces to '::'. * * Example: FF01:0:0:0:0:0:0:101 -> FF01::101 * 0:0:0:0:0:0:0:1 -> ::1 * * @see \WpOrg\Requests\Ipv6::uncompress() * * @param string $ip An IPv6 address * @return string The compressed IPv6 address */ public static function compress($ip) { // Prepare the IP to be compressed. // Note: Input validation is handled in the `uncompress()` method, which is the first call made in this method. $ip = self::uncompress($ip); $ip_parts = self::split_v6_v4($ip); // Replace all leading zeros $ip_parts[0] = preg_replace('/(^|:)0+([0-9])/', '\1\2', $ip_parts[0]); // Find bunches of zeros if (preg_match_all('/(?:^|:)(?:0(?::|$))+/', $ip_parts[0], $matches, PREG_OFFSET_CAPTURE)) { $max = 0; $pos = null; foreach ($matches[0] as $match) { if (strlen($match[0]) > $max) { $max = strlen($match[0]); $pos = $match[1]; } } $ip_parts[0] = substr_replace($ip_parts[0], '::', $pos, $max); } if ($ip_parts[1] !== '') { return implode(':', $ip_parts); } else { return $ip_parts[0]; } } /** * Splits an IPv6 address into the IPv6 and IPv4 representation parts * * RFC 4291 allows you to represent the last two parts of an IPv6 address * using the standard IPv4 representation * * Example: 0:0:0:0:0:0:13.1.68.3 * 0:0:0:0:0:FFFF:129.144.52.38 * * @param string $ip An IPv6 address * @return string[] [0] contains the IPv6 represented part, and [1] the IPv4 represented part */ private static function split_v6_v4($ip) { if (strpos($ip, '.') !== false) { $pos = strrpos($ip, ':'); $ipv6_part = substr($ip, 0, $pos); $ipv4_part = substr($ip, $pos + 1); return [$ipv6_part, $ipv4_part]; } else { return [$ip, '']; } } /** * Checks an IPv6 address * * Checks if the given IP is a valid IPv6 address * * @param string $ip An IPv6 address * @return bool true if $ip is a valid IPv6 address */ public static function check_ipv6($ip) { // Note: Input validation is handled in the `uncompress()` method, which is the first call made in this method. $ip = self::uncompress($ip); list($ipv6, $ipv4) = self::split_v6_v4($ip); $ipv6 = explode(':', $ipv6); $ipv4 = explode('.', $ipv4); if ((count($ipv6) === 8 && count($ipv4) === 1) || (count($ipv6) === 6 && count($ipv4) === 4)) { foreach ($ipv6 as $ipv6_part) { // The section can't be empty if ($ipv6_part === '') { return false; } // Nor can it be over four characters if (strlen($ipv6_part) > 4) { return false; } // Remove leading zeros (this is safe because of the above) $ipv6_part = ltrim($ipv6_part, '0'); if ($ipv6_part === '') { $ipv6_part = '0'; } // Check the value is valid $value = hexdec($ipv6_part); if (dechex($value) !== strtolower($ipv6_part) || $value < 0 || $value > 0xFFFF) { return false; } } if (count($ipv4) === 4) { foreach ($ipv4 as $ipv4_part) { $value = (int) $ipv4_part; if ((string) $value !== $ipv4_part || $value < 0 || $value > 0xFF) { return false; } } } return true; } else { return false; } } } Autoload.php000064400000022167152505214730007041 0ustar00 '\WpOrg\Requests\Auth', 'requests_hooker' => '\WpOrg\Requests\HookManager', 'requests_proxy' => '\WpOrg\Requests\Proxy', 'requests_transport' => '\WpOrg\Requests\Transport', // Classes. 'requests_cookie' => '\WpOrg\Requests\Cookie', 'requests_exception' => '\WpOrg\Requests\Exception', 'requests_hooks' => '\WpOrg\Requests\Hooks', 'requests_idnaencoder' => '\WpOrg\Requests\IdnaEncoder', 'requests_ipv6' => '\WpOrg\Requests\Ipv6', 'requests_iri' => '\WpOrg\Requests\Iri', 'requests_response' => '\WpOrg\Requests\Response', 'requests_session' => '\WpOrg\Requests\Session', 'requests_ssl' => '\WpOrg\Requests\Ssl', 'requests_auth_basic' => '\WpOrg\Requests\Auth\Basic', 'requests_cookie_jar' => '\WpOrg\Requests\Cookie\Jar', 'requests_proxy_http' => '\WpOrg\Requests\Proxy\Http', 'requests_response_headers' => '\WpOrg\Requests\Response\Headers', 'requests_transport_curl' => '\WpOrg\Requests\Transport\Curl', 'requests_transport_fsockopen' => '\WpOrg\Requests\Transport\Fsockopen', 'requests_utility_caseinsensitivedictionary' => '\WpOrg\Requests\Utility\CaseInsensitiveDictionary', 'requests_utility_filterediterator' => '\WpOrg\Requests\Utility\FilteredIterator', 'requests_exception_http' => '\WpOrg\Requests\Exception\Http', 'requests_exception_transport' => '\WpOrg\Requests\Exception\Transport', 'requests_exception_transport_curl' => '\WpOrg\Requests\Exception\Transport\Curl', 'requests_exception_http_304' => '\WpOrg\Requests\Exception\Http\Status304', 'requests_exception_http_305' => '\WpOrg\Requests\Exception\Http\Status305', 'requests_exception_http_306' => '\WpOrg\Requests\Exception\Http\Status306', 'requests_exception_http_400' => '\WpOrg\Requests\Exception\Http\Status400', 'requests_exception_http_401' => '\WpOrg\Requests\Exception\Http\Status401', 'requests_exception_http_402' => '\WpOrg\Requests\Exception\Http\Status402', 'requests_exception_http_403' => '\WpOrg\Requests\Exception\Http\Status403', 'requests_exception_http_404' => '\WpOrg\Requests\Exception\Http\Status404', 'requests_exception_http_405' => '\WpOrg\Requests\Exception\Http\Status405', 'requests_exception_http_406' => '\WpOrg\Requests\Exception\Http\Status406', 'requests_exception_http_407' => '\WpOrg\Requests\Exception\Http\Status407', 'requests_exception_http_408' => '\WpOrg\Requests\Exception\Http\Status408', 'requests_exception_http_409' => '\WpOrg\Requests\Exception\Http\Status409', 'requests_exception_http_410' => '\WpOrg\Requests\Exception\Http\Status410', 'requests_exception_http_411' => '\WpOrg\Requests\Exception\Http\Status411', 'requests_exception_http_412' => '\WpOrg\Requests\Exception\Http\Status412', 'requests_exception_http_413' => '\WpOrg\Requests\Exception\Http\Status413', 'requests_exception_http_414' => '\WpOrg\Requests\Exception\Http\Status414', 'requests_exception_http_415' => '\WpOrg\Requests\Exception\Http\Status415', 'requests_exception_http_416' => '\WpOrg\Requests\Exception\Http\Status416', 'requests_exception_http_417' => '\WpOrg\Requests\Exception\Http\Status417', 'requests_exception_http_418' => '\WpOrg\Requests\Exception\Http\Status418', 'requests_exception_http_428' => '\WpOrg\Requests\Exception\Http\Status428', 'requests_exception_http_429' => '\WpOrg\Requests\Exception\Http\Status429', 'requests_exception_http_431' => '\WpOrg\Requests\Exception\Http\Status431', 'requests_exception_http_500' => '\WpOrg\Requests\Exception\Http\Status500', 'requests_exception_http_501' => '\WpOrg\Requests\Exception\Http\Status501', 'requests_exception_http_502' => '\WpOrg\Requests\Exception\Http\Status502', 'requests_exception_http_503' => '\WpOrg\Requests\Exception\Http\Status503', 'requests_exception_http_504' => '\WpOrg\Requests\Exception\Http\Status504', 'requests_exception_http_505' => '\WpOrg\Requests\Exception\Http\Status505', 'requests_exception_http_511' => '\WpOrg\Requests\Exception\Http\Status511', 'requests_exception_http_unknown' => '\WpOrg\Requests\Exception\Http\StatusUnknown', ]; /** * Register the autoloader. * * Note: the autoloader is *prepended* in the autoload queue. * This is done to ensure that the Requests 2.0 autoloader takes precedence * over a potentially (dependency-registered) Requests 1.x autoloader. * * @internal This method contains a safeguard against the autoloader being * registered multiple times. This safeguard uses a global constant to * (hopefully/in most cases) still function correctly, even if the * class would be renamed. * * @return void */ public static function register() { if (defined('REQUESTS_AUTOLOAD_REGISTERED') === false) { spl_autoload_register([self::class, 'load'], true); define('REQUESTS_AUTOLOAD_REGISTERED', true); } } /** * Autoloader. * * @param string $class_name Name of the class name to load. * * @return bool Whether a class was loaded or not. */ public static function load($class_name) { // Check that the class starts with "Requests" (PSR-0) or "WpOrg\Requests" (PSR-4). $psr_4_prefix_pos = strpos($class_name, 'WpOrg\\Requests\\'); if (stripos($class_name, 'Requests') !== 0 && $psr_4_prefix_pos !== 0) { return false; } $class_lower = strtolower($class_name); if ($class_lower === 'requests') { // Reference to the original PSR-0 Requests class. $file = dirname(__DIR__) . '/library/Requests.php'; } elseif ($psr_4_prefix_pos === 0) { // PSR-4 classname. $file = __DIR__ . '/' . strtr(substr($class_name, 15), '\\', '/') . '.php'; } if (isset($file) && file_exists($file)) { include $file; return true; } /* * Okay, so the class starts with "Requests", but we couldn't find the file. * If this is one of the deprecated/renamed PSR-0 classes being requested, * let's alias it to the new name and throw a deprecation notice. */ if (isset(self::$deprecated_classes[$class_lower])) { /* * Integrators who cannot yet upgrade to the PSR-4 class names can silence deprecations * by defining a `REQUESTS_SILENCE_PSR0_DEPRECATIONS` constant and setting it to `true`. * The constant needs to be defined before the first deprecated class is requested * via this autoloader. */ if (!defined('REQUESTS_SILENCE_PSR0_DEPRECATIONS') || REQUESTS_SILENCE_PSR0_DEPRECATIONS !== true) { // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error trigger_error( 'The PSR-0 `Requests_...` class names in the Requests library are deprecated.' . ' Switch to the PSR-4 `WpOrg\Requests\...` class names at your earliest convenience.', E_USER_DEPRECATED ); // Prevent the deprecation notice from being thrown twice. if (!defined('REQUESTS_SILENCE_PSR0_DEPRECATIONS')) { define('REQUESTS_SILENCE_PSR0_DEPRECATIONS', true); } } // Create an alias and let the autoloader recursively kick in to load the PSR-4 class. return class_alias(self::$deprecated_classes[$class_lower], $class_name, true); } return false; } } } File.php000064400000045702152505244670006156 0ustar00> Canonical representation of headers */ private $parsed_headers = []; /** @var array Last known value of $headers property (used to detect external modification) */ private $last_headers = []; /** * @var array Headers as string for BC * @deprecated Use `get_headers()` method. */ public $headers = []; /** * @var ?string Body of the HTTP response * @deprecated Use `get_body_content()` method. */ public $body; /** * @var int Status code of the HTTP response * @deprecated Use `get_status_code()` method. */ public $status_code = 0; /** @var non-negative-int Number of redirect that were already performed during this request sequence. */ public $redirects = 0; /** @var ?string */ public $error; /** * @var int-mask-of Bit mask representing the method used to fetch the file and whether it is a local file or remote file obtained over HTTP. * @deprecated Backend is implementation detail which you should not care about; to see if the file was retrieved over HTTP, check if `get_final_requested_uri()` with `Misc::is_remote_uri()`. */ public $method = \SimplePie\SimplePie::FILE_SOURCE_NONE; /** * @var string The permanent URL or the resource (first URL after the prefix of (only) permanent redirects) * @deprecated Use `get_permanent_uri()` method. */ public $permanent_url; /** @var bool Whether the permanent URL is still writeable (prefix of permanent redirects has not ended) */ private $permanentUrlMutable = true; /** * @param string $url * @param int $timeout * @param int $redirects * @param ?array $headers * @param ?string $useragent * @param bool $force_fsockopen * @param array $curl_options */ public function __construct(string $url, int $timeout = 10, int $redirects = 5, ?array $headers = null, ?string $useragent = null, bool $force_fsockopen = false, array $curl_options = []) { if (function_exists('idn_to_ascii')) { $parsed = \SimplePie\Misc::parse_url($url); if ($parsed['authority'] !== '' && !ctype_print($parsed['authority'])) { $authority = (string) \idn_to_ascii($parsed['authority'], \IDNA_NONTRANSITIONAL_TO_ASCII, \INTL_IDNA_VARIANT_UTS46); $url = \SimplePie\Misc::compress_parse_url($parsed['scheme'], $authority, $parsed['path'], $parsed['query'], null); } } $this->url = $url; if ($this->permanentUrlMutable) { $this->permanent_url = $url; } $this->useragent = $useragent; if (preg_match('/^http(s)?:\/\//i', $url)) { if ($useragent === null) { $useragent = (string) ini_get('user_agent'); $this->useragent = $useragent; } if (!is_array($headers)) { $headers = []; } if (!$force_fsockopen && function_exists('curl_exec')) { $this->method = \SimplePie\SimplePie::FILE_SOURCE_REMOTE | \SimplePie\SimplePie::FILE_SOURCE_CURL; $fp = curl_init(); $headers2 = []; foreach ($headers as $key => $value) { $headers2[] = "$key: $value"; } if (isset($curl_options[CURLOPT_HTTPHEADER])) { if (is_array($curl_options[CURLOPT_HTTPHEADER])) { $headers2 = array_merge($headers2, $curl_options[CURLOPT_HTTPHEADER]); } unset($curl_options[CURLOPT_HTTPHEADER]); } if (version_compare(\SimplePie\Misc::get_curl_version(), '7.10.5', '>=')) { curl_setopt($fp, CURLOPT_ENCODING, ''); } curl_setopt($fp, CURLOPT_URL, $url); curl_setopt($fp, CURLOPT_HEADER, 1); curl_setopt($fp, CURLOPT_RETURNTRANSFER, 1); curl_setopt($fp, CURLOPT_FAILONERROR, 1); curl_setopt($fp, CURLOPT_TIMEOUT, $timeout); curl_setopt($fp, CURLOPT_CONNECTTIMEOUT, $timeout); curl_setopt($fp, CURLOPT_REFERER, \SimplePie\Misc::url_remove_credentials($url)); curl_setopt($fp, CURLOPT_USERAGENT, $useragent); curl_setopt($fp, CURLOPT_HTTPHEADER, $headers2); foreach ($curl_options as $curl_param => $curl_value) { curl_setopt($fp, $curl_param, $curl_value); } $responseHeaders = curl_exec($fp); if (curl_errno($fp) === CURLE_WRITE_ERROR || curl_errno($fp) === CURLE_BAD_CONTENT_ENCODING) { curl_setopt($fp, CURLOPT_ENCODING, 'none'); $responseHeaders = curl_exec($fp); } $this->status_code = curl_getinfo($fp, CURLINFO_HTTP_CODE); if (curl_errno($fp)) { $this->error = 'cURL error ' . curl_errno($fp) . ': ' . curl_error($fp); $this->success = false; } else { // Use the updated url provided by curl_getinfo after any redirects. if ($info = curl_getinfo($fp)) { $this->url = $info['url']; } // For PHPStan: We already checked that error did not occur. assert(is_array($info) && $info['redirect_count'] >= 0); if (\PHP_VERSION_ID < 80000) { curl_close($fp); } $responseHeaders = \SimplePie\HTTP\Parser::prepareHeaders((string) $responseHeaders, $info['redirect_count'] + 1); $parser = new \SimplePie\HTTP\Parser($responseHeaders, true); if ($parser->parse()) { $this->set_headers($parser->headers); $this->body = $parser->body; $this->status_code = $parser->status_code; if ((in_array($this->status_code, [300, 301, 302, 303, 307]) || $this->status_code > 307 && $this->status_code < 400) && ($locationHeader = $this->get_header_line('location')) !== '' && $this->redirects < $redirects) { $this->redirects++; $location = \SimplePie\Misc::absolutize_url($locationHeader, $url); if ($location === false) { $this->error = "Invalid redirect location, trying to base “{$locationHeader}” onto “{$url}”"; $this->success = false; return; } $this->permanentUrlMutable = $this->permanentUrlMutable && ($this->status_code == 301 || $this->status_code == 308); $this->__construct($location, $timeout, $redirects, $headers, $useragent, $force_fsockopen, $curl_options); return; } } } } else { $this->method = \SimplePie\SimplePie::FILE_SOURCE_REMOTE | \SimplePie\SimplePie::FILE_SOURCE_FSOCKOPEN; if (($url_parts = parse_url($url)) === false) { throw new \InvalidArgumentException('Malformed URL: ' . $url); } if (!isset($url_parts['host'])) { throw new \InvalidArgumentException('Missing hostname: ' . $url); } $socket_host = $url_parts['host']; if (isset($url_parts['scheme']) && strtolower($url_parts['scheme']) === 'https') { $socket_host = 'ssl://' . $socket_host; $url_parts['port'] = 443; } if (!isset($url_parts['port'])) { $url_parts['port'] = 80; } $fp = @fsockopen($socket_host, $url_parts['port'], $errno, $errstr, $timeout); if (!$fp) { $this->error = 'fsockopen error: ' . $errstr; $this->success = false; } else { stream_set_timeout($fp, $timeout); if (isset($url_parts['path'])) { if (isset($url_parts['query'])) { $get = "$url_parts[path]?$url_parts[query]"; } else { $get = $url_parts['path']; } } else { $get = '/'; } $out = "GET $get HTTP/1.1\r\n"; $out .= "Host: $url_parts[host]\r\n"; $out .= "User-Agent: $useragent\r\n"; if (extension_loaded('zlib')) { $out .= "Accept-Encoding: x-gzip,gzip,deflate\r\n"; } if (isset($url_parts['user']) && isset($url_parts['pass'])) { $out .= "Authorization: Basic " . base64_encode("$url_parts[user]:$url_parts[pass]") . "\r\n"; } foreach ($headers as $key => $value) { $out .= "$key: $value\r\n"; } $out .= "Connection: Close\r\n\r\n"; fwrite($fp, $out); $info = stream_get_meta_data($fp); $responseHeaders = ''; while (!$info['eof'] && !$info['timed_out']) { $responseHeaders .= fread($fp, 1160); $info = stream_get_meta_data($fp); } if (!$info['timed_out']) { $parser = new \SimplePie\HTTP\Parser($responseHeaders, true); if ($parser->parse()) { $this->set_headers($parser->headers); $this->body = $parser->body; $this->status_code = $parser->status_code; if ((in_array($this->status_code, [300, 301, 302, 303, 307]) || $this->status_code > 307 && $this->status_code < 400) && ($locationHeader = $this->get_header_line('location')) !== '' && $this->redirects < $redirects) { $this->redirects++; $location = \SimplePie\Misc::absolutize_url($locationHeader, $url); $this->permanentUrlMutable = $this->permanentUrlMutable && ($this->status_code == 301 || $this->status_code == 308); if ($location === false) { $this->error = "Invalid redirect location, trying to base “{$locationHeader}” onto “{$url}”"; $this->success = false; return; } $this->__construct($location, $timeout, $redirects, $headers, $useragent, $force_fsockopen, $curl_options); return; } if (($contentEncodingHeader = $this->get_header_line('content-encoding')) !== '') { // Hey, we act dumb elsewhere, so let's do that here too switch (strtolower(trim($contentEncodingHeader, "\x09\x0A\x0D\x20"))) { case 'gzip': case 'x-gzip': if (($decompressed = gzdecode($this->body)) === false) { $this->error = 'Unable to decode HTTP "gzip" stream'; $this->success = false; } else { $this->body = $decompressed; } break; case 'deflate': if (($decompressed = gzinflate($this->body)) !== false) { $this->body = $decompressed; } elseif (($decompressed = gzuncompress($this->body)) !== false) { $this->body = $decompressed; } elseif (($decompressed = gzdecode($this->body)) !== false) { $this->body = $decompressed; } else { $this->error = 'Unable to decode HTTP "deflate" stream'; $this->success = false; } break; default: $this->error = 'Unknown content coding'; $this->success = false; } } } } else { $this->error = 'fsocket timed out'; $this->success = false; } fclose($fp); } } } else { $this->method = \SimplePie\SimplePie::FILE_SOURCE_LOCAL | \SimplePie\SimplePie::FILE_SOURCE_FILE_GET_CONTENTS; if (empty($url) || !is_readable($url) || false === $filebody = file_get_contents($url)) { $this->body = ''; $this->error = sprintf('file "%s" is not readable', $url); $this->success = false; } else { $this->body = $filebody; $this->status_code = 200; } } if ($this->success) { assert($this->body !== null); // For PHPStan // Leading whitespace may cause XML parsing errors (XML declaration cannot be preceded by anything other than BOM) so we trim it. // Note that unlike built-in `trim` function’s default settings, we do not trim `\x00` to avoid breaking characters in UTF-16 or UTF-32 encoded strings. // We also only do that when the whitespace is followed by `<`, so that we do not break e.g. UTF-16LE encoded whitespace like `\n\x00` in half. $this->body = preg_replace('/^[ \n\r\t\v]+body); } } public function get_permanent_uri(): string { return (string) $this->permanent_url; } public function get_final_requested_uri(): string { return (string) $this->url; } public function get_status_code(): int { return (int) $this->status_code; } public function get_headers(): array { $this->maybe_update_headers(); return $this->parsed_headers; } public function has_header(string $name): bool { $this->maybe_update_headers(); return $this->get_header($name) !== []; } public function get_header(string $name): array { $this->maybe_update_headers(); return $this->parsed_headers[strtolower($name)] ?? []; } public function with_header(string $name, $value) { $this->maybe_update_headers(); $new = clone $this; $newHeader = [ strtolower($name) => (array) $value, ]; $new->set_headers($newHeader + $this->get_headers()); return $new; } public function get_header_line(string $name): string { $this->maybe_update_headers(); return implode(', ', $this->get_header($name)); } public function get_body_content(): string { return (string) $this->body; } /** * Check if the $headers property was changed and update the internal state accordingly. */ private function maybe_update_headers(): void { if ($this->headers !== $this->last_headers) { $this->parsed_headers = array_map( function (string $header_line): array { if (strpos($header_line, ',') === false) { return [$header_line]; } else { return array_map('trim', explode(',', $header_line)); } }, $this->headers ); } $this->last_headers = $this->headers; } /** * Sets headers internally. * * @param array> $headers */ private function set_headers(array $headers): void { $this->parsed_headers = $headers; $this->headers = self::flatten_headers($headers); $this->last_headers = $this->headers; } /** * Converts PSR-7 compatible headers into a legacy format. * * @param array> $headers * * @return array */ private function flatten_headers(array $headers): array { return array_map(function (array $values): string { return implode(',', $values); }, $headers); } /** * Create a File instance from another Response * * For BC reasons in some places there MUST be a `File` instance * instead of a `Response` implementation * * @see Locator::__construct() * @internal */ final public static function fromResponse(Response $response): self { $headers = []; foreach ($response->get_headers() as $name => $header) { $headers[$name] = implode(', ', $header); } /** @var File */ $file = (new \ReflectionClass(File::class))->newInstanceWithoutConstructor(); $file->url = $response->get_final_requested_uri(); $file->useragent = null; $file->headers = $headers; $file->body = $response->get_body_content(); $file->status_code = $response->get_status_code(); $file->permanent_url = $response->get_permanent_uri(); return $file; } } class_alias('SimplePie\File', 'SimplePie_File'); Parse/Date.php000064400000062206152505244670007224 0ustar00 ordinal day number in the week * * @access protected * @var array> */ public $day = [ // English 'mon' => 1, 'monday' => 1, 'tue' => 2, 'tuesday' => 2, 'wed' => 3, 'wednesday' => 3, 'thu' => 4, 'thursday' => 4, 'fri' => 5, 'friday' => 5, 'sat' => 6, 'saturday' => 6, 'sun' => 7, 'sunday' => 7, // Dutch 'maandag' => 1, 'dinsdag' => 2, 'woensdag' => 3, 'donderdag' => 4, 'vrijdag' => 5, 'zaterdag' => 6, 'zondag' => 7, // French 'lundi' => 1, 'mardi' => 2, 'mercredi' => 3, 'jeudi' => 4, 'vendredi' => 5, 'samedi' => 6, 'dimanche' => 7, // German 'montag' => 1, 'mo' => 1, 'dienstag' => 2, 'di' => 2, 'mittwoch' => 3, 'mi' => 3, 'donnerstag' => 4, 'do' => 4, 'freitag' => 5, 'fr' => 5, 'samstag' => 6, 'sa' => 6, 'sonnabend' => 6, // AFAIK no short form for sonnabend 'so' => 7, 'sonntag' => 7, // Italian 'lunedì' => 1, 'martedì' => 2, 'mercoledì' => 3, 'giovedì' => 4, 'venerdì' => 5, 'sabato' => 6, 'domenica' => 7, // Spanish 'lunes' => 1, 'martes' => 2, 'miércoles' => 3, 'jueves' => 4, 'viernes' => 5, 'sábado' => 6, 'domingo' => 7, // Finnish 'maanantai' => 1, 'tiistai' => 2, 'keskiviikko' => 3, 'torstai' => 4, 'perjantai' => 5, 'lauantai' => 6, 'sunnuntai' => 7, // Hungarian 'hétfő' => 1, 'kedd' => 2, 'szerda' => 3, 'csütörtok' => 4, 'péntek' => 5, 'szombat' => 6, 'vasárnap' => 7, // Greek 'Δευ' => 1, 'Τρι' => 2, 'Τετ' => 3, 'Πεμ' => 4, 'Παρ' => 5, 'Σαβ' => 6, 'Κυρ' => 7, // Russian 'Пн.' => 1, 'Вт.' => 2, 'Ср.' => 3, 'Чт.' => 4, 'Пт.' => 5, 'Сб.' => 6, 'Вс.' => 7, ]; /** * List of months, calendar month name => calendar month number * * @access protected * @var array> */ public $month = [ // English 'jan' => 1, 'january' => 1, 'feb' => 2, 'february' => 2, 'mar' => 3, 'march' => 3, 'apr' => 4, 'april' => 4, 'may' => 5, // No long form of May 'jun' => 6, 'june' => 6, 'jul' => 7, 'july' => 7, 'aug' => 8, 'august' => 8, 'sep' => 9, 'september' => 9, 'oct' => 10, 'october' => 10, 'nov' => 11, 'november' => 11, 'dec' => 12, 'december' => 12, // Dutch 'januari' => 1, 'februari' => 2, 'maart' => 3, // 'april' => 4, 'mei' => 5, 'juni' => 6, 'juli' => 7, 'augustus' => 8, // 'september' => 9, 'oktober' => 10, // 'november' => 11, // 'december' => 12, // French 'janvier' => 1, 'février' => 2, 'mars' => 3, 'avril' => 4, 'mai' => 5, 'juin' => 6, 'juillet' => 7, 'août' => 8, 'septembre' => 9, 'octobre' => 10, 'novembre' => 11, 'décembre' => 12, // German 'januar' => 1, // 'jan' => 1, 'februar' => 2, // 'feb' => 2, 'märz' => 3, 'mär' => 3, // 'april' => 4, // 'apr' => 4, // 'mai' => 5, // no short form for may // 'juni' => 6, // 'jun' => 6, // 'juli' => 7, // 'jul' => 7, // 'august' => 8, // 'aug' => 8, // 'september' => 9, // 'sep' => 9, // 'oktober' => 10, 'okt' => 10, // 'november' => 11, // 'nov' => 11, 'dezember' => 12, 'dez' => 12, // Italian 'gennaio' => 1, 'febbraio' => 2, 'marzo' => 3, 'aprile' => 4, 'maggio' => 5, 'giugno' => 6, 'luglio' => 7, 'agosto' => 8, 'settembre' => 9, 'ottobre' => 10, // 'novembre' => 11, 'dicembre' => 12, // Spanish 'enero' => 1, 'febrero' => 2, // 'marzo' => 3, 'abril' => 4, 'mayo' => 5, 'junio' => 6, 'julio' => 7, // 'agosto' => 8, 'septiembre' => 9, 'setiembre' => 9, 'octubre' => 10, 'noviembre' => 11, 'diciembre' => 12, // Finnish 'tammikuu' => 1, 'helmikuu' => 2, 'maaliskuu' => 3, 'huhtikuu' => 4, 'toukokuu' => 5, 'kesäkuu' => 6, 'heinäkuu' => 7, 'elokuu' => 8, 'suuskuu' => 9, 'lokakuu' => 10, 'marras' => 11, 'joulukuu' => 12, // Hungarian 'január' => 1, 'február' => 2, 'március' => 3, 'április' => 4, 'május' => 5, 'június' => 6, 'július' => 7, 'augusztus' => 8, 'szeptember' => 9, 'október' => 10, // 'november' => 11, // 'december' => 12, // Greek 'Ιαν' => 1, 'Φεβ' => 2, 'Μάώ' => 3, 'Μαώ' => 3, 'Απρ' => 4, 'Μάι' => 5, 'Μαϊ' => 5, 'Μαι' => 5, 'Ιούν' => 6, 'Ιον' => 6, 'Ιούλ' => 7, 'Ιολ' => 7, 'Αύγ' => 8, 'Αυγ' => 8, 'Σεπ' => 9, 'Οκτ' => 10, 'Νοέ' => 11, 'Δεκ' => 12, // Russian 'Янв' => 1, 'января' => 1, 'Фев' => 2, 'февраля' => 2, 'Мар' => 3, 'марта' => 3, 'Апр' => 4, 'апреля' => 4, 'Май' => 5, 'мая' => 5, 'Июн' => 6, 'июня' => 6, 'Июл' => 7, 'июля' => 7, 'Авг' => 8, 'августа' => 8, 'Сен' => 9, 'сентября' => 9, 'Окт' => 10, 'октября' => 10, 'Ноя' => 11, 'ноября' => 11, 'Дек' => 12, 'декабря' => 12, ]; /** * List of timezones, abbreviation => offset from UTC * * @access protected * @var array */ public $timezone = [ 'ACDT' => 37800, 'ACIT' => 28800, 'ACST' => 34200, 'ACT' => -18000, 'ACWDT' => 35100, 'ACWST' => 31500, 'AEDT' => 39600, 'AEST' => 36000, 'AFT' => 16200, 'AKDT' => -28800, 'AKST' => -32400, 'AMDT' => 18000, 'AMT' => -14400, 'ANAST' => 46800, 'ANAT' => 43200, 'ART' => -10800, 'AZOST' => -3600, 'AZST' => 18000, 'AZT' => 14400, 'BIOT' => 21600, 'BIT' => -43200, 'BOT' => -14400, 'BRST' => -7200, 'BRT' => -10800, 'BST' => 3600, 'BTT' => 21600, 'CAST' => 18000, 'CAT' => 7200, 'CCT' => 23400, 'CDT' => -18000, 'CEDT' => 7200, 'CEST' => 7200, 'CET' => 3600, 'CGST' => -7200, 'CGT' => -10800, 'CHADT' => 49500, 'CHAST' => 45900, 'CIST' => -28800, 'CKT' => -36000, 'CLDT' => -10800, 'CLST' => -14400, 'COT' => -18000, 'CST' => -21600, 'CVT' => -3600, 'CXT' => 25200, 'DAVT' => 25200, 'DTAT' => 36000, 'EADT' => -18000, 'EAST' => -21600, 'EAT' => 10800, 'ECT' => -18000, 'EDT' => -14400, 'EEST' => 10800, 'EET' => 7200, 'EGT' => -3600, 'EKST' => 21600, 'EST' => -18000, 'FJT' => 43200, 'FKDT' => -10800, 'FKST' => -14400, 'FNT' => -7200, 'GALT' => -21600, 'GEDT' => 14400, 'GEST' => 10800, 'GFT' => -10800, 'GILT' => 43200, 'GIT' => -32400, 'GST' => 14400, // 'GST' => -7200, 'GYT' => -14400, 'HAA' => -10800, 'HAC' => -18000, 'HADT' => -32400, 'HAE' => -14400, 'HAP' => -25200, 'HAR' => -21600, 'HAST' => -36000, 'HAT' => -9000, 'HAY' => -28800, 'HKST' => 28800, 'HMT' => 18000, 'HNA' => -14400, 'HNC' => -21600, 'HNE' => -18000, 'HNP' => -28800, 'HNR' => -25200, 'HNT' => -12600, 'HNY' => -32400, 'IRDT' => 16200, 'IRKST' => 32400, 'IRKT' => 28800, 'IRST' => 12600, 'JFDT' => -10800, 'JFST' => -14400, 'JST' => 32400, 'KGST' => 21600, 'KGT' => 18000, 'KOST' => 39600, 'KOVST' => 28800, 'KOVT' => 25200, 'KRAST' => 28800, 'KRAT' => 25200, 'KST' => 32400, 'LHDT' => 39600, 'LHST' => 37800, 'LINT' => 50400, 'LKT' => 21600, 'MAGST' => 43200, 'MAGT' => 39600, 'MAWT' => 21600, 'MDT' => -21600, 'MESZ' => 7200, 'MEZ' => 3600, 'MHT' => 43200, 'MIT' => -34200, 'MNST' => 32400, 'MSDT' => 14400, 'MSST' => 10800, 'MST' => -25200, 'MUT' => 14400, 'MVT' => 18000, 'MYT' => 28800, 'NCT' => 39600, 'NDT' => -9000, 'NFT' => 41400, 'NMIT' => 36000, 'NOVST' => 25200, 'NOVT' => 21600, 'NPT' => 20700, 'NRT' => 43200, 'NST' => -12600, 'NUT' => -39600, 'NZDT' => 46800, 'NZST' => 43200, 'OMSST' => 25200, 'OMST' => 21600, 'PDT' => -25200, 'PET' => -18000, 'PETST' => 46800, 'PETT' => 43200, 'PGT' => 36000, 'PHOT' => 46800, 'PHT' => 28800, 'PKT' => 18000, 'PMDT' => -7200, 'PMST' => -10800, 'PONT' => 39600, 'PST' => -28800, 'PWT' => 32400, 'PYST' => -10800, 'PYT' => -14400, 'RET' => 14400, 'ROTT' => -10800, 'SAMST' => 18000, 'SAMT' => 14400, 'SAST' => 7200, 'SBT' => 39600, 'SCDT' => 46800, 'SCST' => 43200, 'SCT' => 14400, 'SEST' => 3600, 'SGT' => 28800, 'SIT' => 28800, 'SRT' => -10800, 'SST' => -39600, 'SYST' => 10800, 'SYT' => 7200, 'TFT' => 18000, 'THAT' => -36000, 'TJT' => 18000, 'TKT' => -36000, 'TMT' => 18000, 'TOT' => 46800, 'TPT' => 32400, 'TRUT' => 36000, 'TVT' => 43200, 'TWT' => 28800, 'UYST' => -7200, 'UYT' => -10800, 'UZT' => 18000, 'VET' => -14400, 'VLAST' => 39600, 'VLAT' => 36000, 'VOST' => 21600, 'VUT' => 39600, 'WAST' => 7200, 'WAT' => 3600, 'WDT' => 32400, 'WEST' => 3600, 'WFT' => 43200, 'WIB' => 25200, 'WIT' => 32400, 'WITA' => 28800, 'WKST' => 18000, 'WST' => 28800, 'YAKST' => 36000, 'YAKT' => 32400, 'YAPT' => 36000, 'YEKST' => 21600, 'YEKT' => 18000, ]; /** * Cached PCRE for Date::$day * * @access protected * @var string */ public $day_pcre; /** * Cached PCRE for Date::$month * * @access protected * @var string */ public $month_pcre; /** * Array of user-added callback methods * * @access private * @var array */ public $built_in = []; /** * Array of user-added callback methods * * @access private * @var array */ public $user = []; /** * Create new Date object, and set self::day_pcre, * self::month_pcre, and self::built_in * * @access private */ public function __construct() { $this->day_pcre = '(' . implode('|', array_keys($this->day)) . ')'; $this->month_pcre = '(' . implode('|', array_keys($this->month)) . ')'; static $cache; if (!isset($cache[get_class($this)])) { $all_methods = get_class_methods($this); foreach ($all_methods as $method) { if (strtolower(substr($method, 0, 5)) === 'date_') { $cache[get_class($this)][] = $method; } } } foreach ($cache[get_class($this)] as $method) { $this->built_in[] = $method; } } /** * Get the object * * @access public * @return Date */ public static function get() { static $object; if (!$object) { $object = new Date(); } return $object; } /** * Parse a date * * @final * @access public * @param string $date Date to parse * @return int|false Timestamp corresponding to date string, or false on failure */ public function parse(string $date) { foreach ($this->user as $method) { if (($returned = call_user_func($method, $date)) !== false) { return (int) $returned; } } foreach ($this->built_in as $method) { // TODO: we should really check this in constructor but that would require private properties. /** @var callable(string): (int|false) */ $callable = [$this, $method]; if (($returned = call_user_func($callable, $date)) !== false) { return $returned; } } return false; } /** * Add a callback method to parse a date * * @final * @access public * @param callable $callback * @return void */ public function add_callback(callable $callback) { $this->user[] = $callback; } /** * Parse a superset of W3C-DTF (allows hyphens and colons to be omitted, as * well as allowing any of upper or lower case "T", horizontal tabs, or * spaces to be used as the time separator (including more than one)) * * @access protected * @param string $date * @return int|false Timestamp */ public function date_w3cdtf(string $date) { $pcre = <<<'PCRE' / ^ (?P[0-9]{4}) (?: -? (?P[0-9]{2}) (?: -? (?P[0-9]{2}) (?: [Tt\x09\x20]+ (?P[0-9]{2}) (?: :? (?P[0-9]{2}) (?: :? (?P[0-9]{2}) (?: . (?P[0-9]*) )? )? )? (?: (?PZ) | (?P[+\-]) (?P[0-9]{1,2}) :? (?P[0-9]{1,2}) ) )? )? )? $ /x PCRE; if (preg_match($pcre, $date, $match)) { // Fill in empty matches and convert to proper types. $year = (int) $match['year']; $month = isset($match['month']) ? (int) $match['month'] : 1; $day = isset($match['day']) ? (int) $match['day'] : 1; $hour = isset($match['hour']) ? (int) $match['hour'] : 0; $minute = isset($match['minute']) ? (int) $match['minute'] : 0; $second = isset($match['second']) ? (int) $match['second'] : 0; $second_fraction = isset($match['second_fraction']) ? ((int) $match['second_fraction']) / (10 ** strlen($match['second_fraction'])) : 0; $tz_sign = ($match['tz_sign'] ?? '') === '-' ? -1 : 1; $tz_hour = isset($match['tz_hour']) ? (int) $match['tz_hour'] : 0; $tz_minute = isset($match['tz_minute']) ? (int) $match['tz_minute'] : 0; // Numeric timezone $timezone = $tz_hour * 3600; $timezone += $tz_minute * 60; $timezone *= $tz_sign; // Convert the number of seconds to an integer, taking decimals into account $second = (int) round($second + $second_fraction); return gmmktime($hour, $minute, $second, $month, $day, $year) - $timezone; } return false; } /** * Remove RFC822 comments * * @access protected * @param string $string Data to strip comments from * @return string Comment stripped string */ public function remove_rfc2822_comments(string $string) { $position = 0; $length = strlen($string); $depth = 0; $output = ''; while ($position < $length && ($pos = strpos($string, '(', $position)) !== false) { $output .= substr($string, $position, $pos - $position); $position = $pos + 1; if ($pos === 0 || $string[$pos - 1] !== '\\') { $depth++; while ($depth && $position < $length) { $position += strcspn($string, '()', $position); if ($string[$position - 1] === '\\') { $position++; continue; } elseif (isset($string[$position])) { switch ($string[$position]) { case '(': $depth++; break; case ')': $depth--; break; } $position++; } else { break; } } } else { $output .= '('; } } $output .= substr($string, $position); return $output; } /** * Parse RFC2822's date format * * @access protected * @param string $date * @return int|false Timestamp */ public function date_rfc2822(string $date) { static $pcre; if (!$pcre) { $wsp = '[\x09\x20]'; $fws = '(?:' . $wsp . '+|' . $wsp . '*(?:\x0D\x0A' . $wsp . '+)+)'; $optional_fws = $fws . '?'; $day_name = $this->day_pcre; $month = $this->month_pcre; $day = '([0-9]{1,2})'; $hour = $minute = $second = '([0-9]{2})'; $year = '([0-9]{2,4})'; $num_zone = '([+\-])([0-9]{2})([0-9]{2})'; $character_zone = '([A-Z]{1,5})'; $zone = '(?:' . $num_zone . '|' . $character_zone . ')'; $pcre = '/(?:' . $optional_fws . $day_name . $optional_fws . ',)?' . $optional_fws . $day . $fws . $month . $fws . $year . $fws . $hour . $optional_fws . ':' . $optional_fws . $minute . '(?:' . $optional_fws . ':' . $optional_fws . $second . ')?' . $fws . $zone . '/i'; } if (preg_match($pcre, $this->remove_rfc2822_comments($date), $match)) { /* Capturing subpatterns: 1: Day name 2: Day 3: Month 4: Year 5: Hour 6: Minute 7: Second 8: Timezone ± 9: Timezone hours 10: Timezone minutes 11: Alphabetic timezone */ $day = (int) $match[2]; // Find the month number $month = $this->month[strtolower($match[3])]; $year = (int) $match[4]; $hour = (int) $match[5]; $minute = (int) $match[6]; // Second is optional, if it is empty set it to zero $second = (int) $match[7]; $tz_sign = $match[8]; $tz_hour = (int) $match[9]; $tz_minute = (int) $match[10]; $tz_code = isset($match[11]) ? strtoupper($match[11]) : ''; // Numeric timezone if ($tz_sign !== '') { $timezone = $tz_hour * 3600; $timezone += $tz_minute * 60; if ($tz_sign === '-') { $timezone = 0 - $timezone; } } // Character timezone elseif (isset($this->timezone[$tz_code])) { $timezone = $this->timezone[$tz_code]; } // Assume everything else to be -0000 else { $timezone = 0; } // Deal with 2/3 digit years if ($year < 50) { $year += 2000; } elseif ($year < 1000) { $year += 1900; } return gmmktime($hour, $minute, $second, $month, $day, $year) - $timezone; } return false; } /** * Parse RFC850's date format * * @access protected * @param string $date * @return int|false Timestamp */ public function date_rfc850(string $date) { static $pcre; if (!$pcre) { $space = '[\x09\x20]+'; $day_name = $this->day_pcre; $month = $this->month_pcre; $day = '([0-9]{1,2})'; $year = $hour = $minute = $second = '([0-9]{2})'; $zone = '([A-Z]{1,5})'; $pcre = '/^' . $day_name . ',' . $space . $day . '-' . $month . '-' . $year . $space . $hour . ':' . $minute . ':' . $second . $space . $zone . '$/i'; } if (preg_match($pcre, $date, $match)) { /* Capturing subpatterns: 1: Day name 2: Day 3: Month 4: Year 5: Hour 6: Minute 7: Second 8: Timezone */ $day = (int) $match[2]; // Month $month = $this->month[strtolower($match[3])]; $year = (int) $match[4]; $hour = (int) $match[5]; $minute = (int) $match[6]; // Second is optional, if it is empty set it to zero $second = (int) $match[7]; $tz_code = strtoupper($match[8]); // Character timezone if (isset($this->timezone[$tz_code])) { $timezone = $this->timezone[$tz_code]; } // Assume everything else to be -0000 else { $timezone = 0; } // Deal with 2 digit year if ($year < 50) { $year += 2000; } else { $year += 1900; } return gmmktime($hour, $minute, $second, $month, $day, $year) - $timezone; } return false; } /** * Parse C99's asctime()'s date format * * @access protected * @param string $date * @return int|false Timestamp */ public function date_asctime(string $date) { static $pcre; if (!$pcre) { $space = '[\x09\x20]+'; $wday_name = $this->day_pcre; $mon_name = $this->month_pcre; $day = '([0-9]{1,2})'; $hour = $sec = $min = '([0-9]{2})'; $year = '([0-9]{4})'; $terminator = '\x0A?\x00?'; $pcre = '/^' . $wday_name . $space . $mon_name . $space . $day . $space . $hour . ':' . $min . ':' . $sec . $space . $year . $terminator . '$/i'; } if (preg_match($pcre, $date, $match)) { /* Capturing subpatterns: 1: Day name 2: Month 3: Day 4: Hour 5: Minute 6: Second 7: Year */ $month = $this->month[strtolower($match[2])]; return gmmktime((int) $match[4], (int) $match[5], (int) $match[6], $month, (int) $match[3], (int) $match[7]); } return false; } /** * Parse dates using strtotime() * * @access protected * @param string $date * @return int|false Timestamp */ public function date_strtotime(string $date) { $strtotime = strtotime($date); if ($strtotime === -1 || $strtotime === false) { return false; } return $strtotime; } } class_alias('SimplePie\Parse\Date', 'SimplePie_Parse_Date'); Category.php000064400000004650152505244670007051 0ustar00 * subject for * * @var string|null * @see get_type() */ public $type; /** * Constructor, used to input the data * * @param string|null $term * @param string|null $scheme * @param string|null $label * @param string|null $type */ public function __construct(?string $term = null, ?string $scheme = null, ?string $label = null, ?string $type = null) { $this->term = $term; $this->scheme = $scheme; $this->label = $label; $this->type = $type; } /** * String-ified version * * @return string */ public function __toString() { // There is no $this->data here return md5(serialize($this)); } /** * Get the category identifier * * @return string|null */ public function get_term() { return $this->term; } /** * Get the categorization scheme identifier * * @return string|null */ public function get_scheme() { return $this->scheme; } /** * Get the human readable label * * @param bool $strict * @return string|null */ public function get_label(bool $strict = false) { if ($this->label === null && $strict !== true) { return $this->get_term(); } return $this->label; } /** * Get the category type * * @return string|null */ public function get_type() { return $this->type; } } class_alias('SimplePie\Category', 'SimplePie_Category'); Cache.php000064400000006260152505244670006276 0ustar00> */ protected static $handlers = [ 'mysql' => Cache\MySQL::class, 'memcache' => Cache\Memcache::class, 'memcached' => Cache\Memcached::class, 'redis' => Cache\Redis::class, ]; /** * Don't call the constructor. Please. */ private function __construct() { } /** * Create a new SimplePie\Cache object * * @param string $location URL location (scheme is used to determine handler) * @param string $filename Unique identifier for cache object * @param Base::TYPE_FEED|Base::TYPE_IMAGE $extension 'spi' or 'spc' * @return Base Type of object depends on scheme of `$location` */ public static function get_handler(string $location, string $filename, $extension) { $type = explode(':', $location, 2); $type = $type[0]; if (!empty(self::$handlers[$type])) { $class = self::$handlers[$type]; return new $class($location, $filename, $extension); } return new \SimplePie\Cache\File($location, $filename, $extension); } /** * Create a new SimplePie\Cache object * * @deprecated since SimplePie 1.3.1, use {@see get_handler()} instead * @param string $location * @param string $filename * @param Base::TYPE_FEED|Base::TYPE_IMAGE $extension * @return Base */ public function create(string $location, string $filename, $extension) { trigger_error('Cache::create() has been replaced with Cache::get_handler() since SimplePie 1.3.1, use the registry system instead.', \E_USER_DEPRECATED); return self::get_handler($location, $filename, $extension); } /** * Register a handler * * @param string $type DSN type to register for * @param class-string $class Name of handler class. Must implement Base * @return void */ public static function register(string $type, $class) { self::$handlers[$type] = $class; } /** * Parse a URL into an array * * @param string $url * @return array */ public static function parse_URL(string $url) { $parsedUrl = parse_url($url); if ($parsedUrl === false) { return []; } $params = array_merge($parsedUrl, ['extras' => []]); if (isset($params['query'])) { parse_str($params['query'], $params['extras']); } return $params; } } class_alias('SimplePie\Cache', 'SimplePie_Cache'); Sanitize.php000064400000073221152505244670007062 0ustar00> */ public $add_attributes = ['audio' => ['preload' => 'none'], 'iframe' => ['sandbox' => 'allow-scripts allow-same-origin'], 'video' => ['preload' => 'none']]; /** @var bool */ public $strip_comments = false; /** @var string */ public $output_encoding = 'UTF-8'; /** @var bool */ public $enable_cache = true; /** @var string */ public $cache_location = './cache'; /** @var string&(callable(string): string) */ public $cache_name_function = 'md5'; /** * @var NameFilter */ private $cache_namefilter; /** @var int */ public $timeout = 10; /** @var string */ public $useragent = ''; /** @var bool */ public $force_fsockopen = false; /** @var array */ public $replace_url_attributes = []; /** * @var array Custom curl options * @see SimplePie::set_curl_options() */ private $curl_options = []; /** @var Registry */ public $registry; /** * @var DataCache|null */ private $cache = null; /** * @var int Cache duration (in seconds) */ private $cache_duration = 3600; /** * List of domains for which to force HTTPS. * @see \SimplePie\Sanitize::set_https_domains() * Array is a tree split at DNS levels. Example: * array('biz' => true, 'com' => array('example' => true), 'net' => array('example' => array('www' => true))) * @var true|array>>>>> */ public $https_domains = []; /** * @var Client|null */ private $http_client = null; public function __construct() { // Set defaults $this->set_url_replacements(null); } /** * @return void */ public function remove_div(bool $enable = true) { $this->remove_div = (bool) $enable; } /** * @param string|false $page * @return void */ public function set_image_handler($page = false) { if ($page) { $this->image_handler = (string) $page; } else { $this->image_handler = ''; } } /** * @return void */ public function set_registry(\SimplePie\Registry $registry) { $this->registry = $registry; } /** * @param (string&(callable(string): string))|NameFilter $cache_name_function * @param class-string $cache_class * @return void */ public function pass_cache_data(bool $enable_cache = true, string $cache_location = './cache', $cache_name_function = 'md5', string $cache_class = Cache::class, ?DataCache $cache = null) { $this->enable_cache = $enable_cache; if ($cache_location) { $this->cache_location = $cache_location; } // @phpstan-ignore-next-line Enforce PHPDoc type. if (!is_string($cache_name_function) && !$cache_name_function instanceof NameFilter) { throw new InvalidArgumentException(sprintf( '%s(): Argument #3 ($cache_name_function) must be of type %s', __METHOD__, NameFilter::class ), 1); } // BC: $cache_name_function could be a callable as string if (is_string($cache_name_function)) { // trigger_error(sprintf('Providing $cache_name_function as string in "%s()" is deprecated since SimplePie 1.8.0, provide as "%s" instead.', __METHOD__, NameFilter::class), \E_USER_DEPRECATED); $this->cache_name_function = $cache_name_function; $cache_name_function = new CallableNameFilter($cache_name_function); } $this->cache_namefilter = $cache_name_function; if ($cache !== null) { $this->cache = $cache; } } /** * Set a PSR-18 client and PSR-17 factories * * Allows you to use your own HTTP client implementations. */ final public function set_http_client( ClientInterface $http_client, RequestFactoryInterface $request_factory, UriFactoryInterface $uri_factory ): void { $this->http_client = new Psr18Client($http_client, $request_factory, $uri_factory); } /** * @deprecated since SimplePie 1.9.0, use \SimplePie\Sanitize::set_http_client() instead. * @param class-string $file_class * @param array $curl_options * @return void */ public function pass_file_data(string $file_class = File::class, int $timeout = 10, string $useragent = '', bool $force_fsockopen = false, array $curl_options = []) { // trigger_error(sprintf('SimplePie\Sanitize::pass_file_data() is deprecated since SimplePie 1.9.0, please use "SimplePie\Sanitize::set_http_client()" instead.'), \E_USER_DEPRECATED); if ($timeout) { $this->timeout = $timeout; } if ($useragent) { $this->useragent = $useragent; } if ($force_fsockopen) { $this->force_fsockopen = $force_fsockopen; } $this->curl_options = $curl_options; // Invalidate the registered client. $this->http_client = null; } /** * @param string[]|string|false $tags Set a list of tags to strip, or set empty string to use default tags, or false to strip nothing. * @return void */ public function strip_htmltags($tags = ['base', 'blink', 'body', 'doctype', 'embed', 'font', 'form', 'frame', 'frameset', 'html', 'iframe', 'input', 'marquee', 'meta', 'noscript', 'object', 'param', 'script', 'style']) { if ($tags) { if (is_array($tags)) { $this->strip_htmltags = $tags; } else { $this->strip_htmltags = explode(',', $tags); } } else { $this->strip_htmltags = []; } } /** * @return void */ public function encode_instead_of_strip(bool $encode = false) { $this->encode_instead_of_strip = $encode; } /** * @param string[]|string $attribs * @return void */ public function rename_attributes($attribs = []) { if ($attribs) { if (is_array($attribs)) { $this->rename_attributes = $attribs; } else { $this->rename_attributes = explode(',', $attribs); } } else { $this->rename_attributes = []; } } /** * @param string[]|string $attribs * @return void */ public function strip_attributes($attribs = ['bgsound', 'expr', 'id', 'style', 'onclick', 'onerror', 'onfinish', 'onmouseover', 'onmouseout', 'onfocus', 'onblur', 'lowsrc', 'dynsrc']) { if ($attribs) { if (is_array($attribs)) { $this->strip_attributes = $attribs; } else { $this->strip_attributes = explode(',', $attribs); } } else { $this->strip_attributes = []; } } /** * @param array> $attribs * @return void */ public function add_attributes(array $attribs = ['audio' => ['preload' => 'none'], 'iframe' => ['sandbox' => 'allow-scripts allow-same-origin'], 'video' => ['preload' => 'none']]) { $this->add_attributes = $attribs; } /** * @return void */ public function strip_comments(bool $strip = false) { $this->strip_comments = $strip; } /** * @return void */ public function set_output_encoding(string $encoding = 'UTF-8') { $this->output_encoding = $encoding; } /** * Set element/attribute key/value pairs of HTML attributes * containing URLs that need to be resolved relative to the feed * * Defaults to |a|@href, |area|@href, |audio|@src, |blockquote|@cite, * |del|@cite, |form|@action, |img|@longdesc, |img|@src, |input|@src, * |ins|@cite, |q|@cite, |source|@src, |video|@src * * @since 1.0 * @param array|null $element_attribute Element/attribute key/value pairs, null for default * @return void */ public function set_url_replacements(?array $element_attribute = null) { if ($element_attribute === null) { $element_attribute = [ 'a' => 'href', 'area' => 'href', 'audio' => 'src', 'blockquote' => 'cite', 'del' => 'cite', 'form' => 'action', 'img' => [ 'longdesc', 'src' ], 'input' => 'src', 'ins' => 'cite', 'q' => 'cite', 'source' => 'src', 'video' => [ 'poster', 'src' ] ]; } $this->replace_url_attributes = $element_attribute; } /** * Set the list of domains for which to force HTTPS. * @see \SimplePie\Misc::https_url() * Example array('biz', 'example.com', 'example.org', 'www.example.net'); * * @param string[] $domains list of domain names ['biz', 'example.com', 'example.org', 'www.example.net'] * * @return void */ public function set_https_domains(array $domains) { $this->https_domains = []; foreach ($domains as $domain) { $domain = trim($domain, ". \t\n\r\0\x0B"); $segments = array_reverse(explode('.', $domain)); /** @var true|array>>>>> */ // Needed for PHPStan. $node = &$this->https_domains; foreach ($segments as $segment) {//Build a tree if ($node === true) { break; } if (!isset($node[$segment])) { $node[$segment] = []; } $node = &$node[$segment]; } $node = true; } } /** * Check if the domain is in the list of forced HTTPS. * * @return bool */ protected function is_https_domain(string $domain) { $domain = trim($domain, '. '); $segments = array_reverse(explode('.', $domain)); $node = &$this->https_domains; foreach ($segments as $segment) {//Explore the tree if (isset($node[$segment])) { $node = &$node[$segment]; } else { break; } } return $node === true; } /** * Force HTTPS for selected Web sites. * * @return string */ public function https_url(string $url) { return ( strtolower(substr($url, 0, 7)) === 'http://' && ($parsed = parse_url($url, PHP_URL_HOST)) !== false // Malformed URL && $parsed !== null // Missing host && $this->is_https_domain($parsed) // Should be forced? ) ? substr_replace($url, 's', 4, 0) // Add the 's' to HTTPS : $url; } /** * @param int-mask-of $type * @param string $base * @return string Sanitized data; false if output encoding is changed to something other than UTF-8 and conversion fails */ public function sanitize(string $data, int $type, string $base = '') { $data = trim($data); if ($data !== '' || $type & \SimplePie\SimplePie::CONSTRUCT_IRI) { if ($type & \SimplePie\SimplePie::CONSTRUCT_MAYBE_HTML) { if (preg_match('/(&(#(x[0-9a-fA-F]+|[0-9]+)|[a-zA-Z0-9]+)|<\/[A-Za-z][^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3E]*' . \SimplePie\SimplePie::PCRE_HTML_ATTRIBUTE . '>)/', $data)) { $type |= \SimplePie\SimplePie::CONSTRUCT_HTML; } else { $type |= \SimplePie\SimplePie::CONSTRUCT_TEXT; } } if ($type & \SimplePie\SimplePie::CONSTRUCT_BASE64) { $data = base64_decode($data); } if ($type & (\SimplePie\SimplePie::CONSTRUCT_HTML | \SimplePie\SimplePie::CONSTRUCT_XHTML)) { if (!class_exists('DOMDocument')) { throw new \SimplePie\Exception('DOMDocument not found, unable to use sanitizer'); } $document = new \DOMDocument(); $document->encoding = 'UTF-8'; // PHPStan seems to have trouble resolving int-mask because bitwise // operators are used when operators are used when passing this parameter. // https://github.com/phpstan/phpstan/issues/9384 /** @var int-mask-of $type */ $data = $this->preprocess($data, $type); set_error_handler([Misc::class, 'silence_errors']); $document->loadHTML($data); restore_error_handler(); $xpath = new \DOMXPath($document); // Strip comments if ($this->strip_comments) { /** @var \DOMNodeList<\DOMComment> */ $comments = $xpath->query('//comment()'); foreach ($comments as $comment) { $parentNode = $comment->parentNode; assert($parentNode !== null, 'For PHPStan, comment must have a parent'); $parentNode->removeChild($comment); } } // Strip out HTML tags and attributes that might cause various security problems. // Based on recommendations by Mark Pilgrim at: // https://web.archive.org/web/20110902041826/http://diveintomark.org:80/archives/2003/06/12/how_to_consume_rss_safely if ($this->strip_htmltags) { foreach ($this->strip_htmltags as $tag) { $this->strip_tag($tag, $document, $xpath, $type); } } if ($this->rename_attributes) { foreach ($this->rename_attributes as $attrib) { $this->rename_attr($attrib, $xpath); } } if ($this->strip_attributes) { foreach ($this->strip_attributes as $attrib) { $this->strip_attr($attrib, $xpath); } } if ($this->add_attributes) { foreach ($this->add_attributes as $tag => $valuePairs) { $this->add_attr($tag, $valuePairs, $document); } } // Replace relative URLs $this->base = $base; foreach ($this->replace_url_attributes as $element => $attributes) { $this->replace_urls($document, $element, $attributes); } // If image handling (caching, etc.) is enabled, cache and rewrite all the image tags. if ($this->image_handler !== '' && $this->enable_cache) { $images = $document->getElementsByTagName('img'); foreach ($images as $img) { if ($img->hasAttribute('src')) { $image_url = $this->cache_namefilter->filter($img->getAttribute('src')); $cache = $this->get_cache($image_url); if ($cache->get_data($image_url, false)) { $img->setAttribute('src', $this->image_handler . $image_url); } else { try { $file = $this->get_http_client()->request( Client::METHOD_GET, $img->getAttribute('src'), ['X-FORWARDED-FOR' => $_SERVER['REMOTE_ADDR']] ); } catch (ClientException $th) { continue; } if ((!Misc::is_remote_uri($file->get_final_requested_uri()) || ($file->get_status_code() === 200 || $file->get_status_code() > 206 && $file->get_status_code() < 300))) { if ($cache->set_data($image_url, ['headers' => $file->get_headers(), 'body' => $file->get_body_content()], $this->cache_duration)) { $img->setAttribute('src', $this->image_handler . $image_url); } else { trigger_error("$this->cache_location is not writable. Make sure you've set the correct relative or absolute path, and that the location is server-writable.", E_USER_WARNING); } } } } } } // Get content node $div = null; if (($item = $document->getElementsByTagName('body')->item(0)) !== null) { $div = $item->firstChild; } // Finally, convert to a HTML string $data = trim((string) $document->saveHTML($div)); if ($this->remove_div) { $data = preg_replace('/^/', '', $data); // Cast for PHPStan, it is unable to validate a non-literal regex above. $data = preg_replace('/<\/div>$/', '', (string) $data); } else { $data = preg_replace('/^/', '
', $data); } // Cast for PHPStan, it is unable to validate a non-literal regex above. $data = str_replace('', '', (string) $data); } if ($type & \SimplePie\SimplePie::CONSTRUCT_IRI) { $absolute = $this->registry->call(Misc::class, 'absolutize_url', [$data, $base]); if ($absolute !== false) { $data = $absolute; } } if ($type & (\SimplePie\SimplePie::CONSTRUCT_TEXT | \SimplePie\SimplePie::CONSTRUCT_IRI)) { $data = htmlspecialchars($data, ENT_COMPAT, 'UTF-8'); } if ($this->output_encoding !== 'UTF-8') { // This really returns string|false but changing encoding is uncommon and we are going to deprecate it, so let’s just lie to PHPStan in the interest of cleaner annotations. /** @var string */ $data = $this->registry->call(Misc::class, 'change_encoding', [$data, 'UTF-8', $this->output_encoding]); } } return $data; } /** * @param int-mask-of $type * @return string */ protected function preprocess(string $html, int $type) { $ret = ''; $html = preg_replace('%]*?'.'>%is', '', $html); if ($type & ~\SimplePie\SimplePie::CONSTRUCT_XHTML) { // Atom XHTML constructs are wrapped with a div by default // Note: No protection if $html contains a stray
! $html = '
' . $html . '
'; $ret .= ''; $content_type = 'text/html'; } else { $ret .= ''; $content_type = 'application/xhtml+xml'; } $ret .= ''; $ret .= ''; $ret .= '' . $html . ''; return $ret; } /** * @param array|string $attributes * @return void */ public function replace_urls(DOMDocument $document, string $tag, $attributes) { if (!is_array($attributes)) { $attributes = [$attributes]; } if (!is_array($this->strip_htmltags) || !in_array($tag, $this->strip_htmltags)) { $elements = $document->getElementsByTagName($tag); foreach ($elements as $element) { foreach ($attributes as $attribute) { if ($element->hasAttribute($attribute)) { $value = $this->registry->call(Misc::class, 'absolutize_url', [$element->getAttribute($attribute), $this->base]); if ($value !== false) { $value = $this->https_url($value); $element->setAttribute($attribute, $value); } } } } } } /** * @param array $match * @return string */ public function do_strip_htmltags(array $match) { if ($this->encode_instead_of_strip) { if (isset($match[4]) && !in_array(strtolower($match[1]), ['script', 'style'])) { $match[1] = htmlspecialchars($match[1], ENT_COMPAT, 'UTF-8'); $match[2] = htmlspecialchars($match[2], ENT_COMPAT, 'UTF-8'); return "<$match[1]$match[2]>$match[3]</$match[1]>"; } else { return htmlspecialchars($match[0], ENT_COMPAT, 'UTF-8'); } } elseif (isset($match[4]) && !in_array(strtolower($match[1]), ['script', 'style'])) { return $match[4]; } else { return ''; } } /** * @param int-mask-of $type * @return void */ protected function strip_tag(string $tag, DOMDocument $document, DOMXPath $xpath, int $type) { $elements = $xpath->query('body//' . $tag); if ($elements === false) { throw new \SimplePie\Exception(sprintf( '%s(): Possibly malformed expression, check argument #1 ($tag)', __METHOD__ ), 1); } if ($this->encode_instead_of_strip) { foreach ($elements as $element) { $fragment = $document->createDocumentFragment(); // For elements which aren't script or style, include the tag itself if (!in_array($tag, ['script', 'style'])) { $text = '<' . $tag; if ($element->attributes !== null) { $attrs = []; foreach ($element->attributes as $name => $attr) { $value = $attr->value; // In XHTML, empty values should never exist, so we repeat the value if (empty($value) && ($type & \SimplePie\SimplePie::CONSTRUCT_XHTML)) { $value = $name; } // For HTML, empty is fine elseif (empty($value) && ($type & \SimplePie\SimplePie::CONSTRUCT_HTML)) { $attrs[] = $name; continue; } // Standard attribute text $attrs[] = $name . '="' . $attr->value . '"'; } $text .= ' ' . implode(' ', $attrs); } $text .= '>'; $fragment->appendChild(new \DOMText($text)); } $number = $element->childNodes->length; for ($i = $number; $i > 0; $i--) { if (($child = $element->childNodes->item(0)) !== null) { $fragment->appendChild($child); } } if (!in_array($tag, ['script', 'style'])) { $fragment->appendChild(new \DOMText('')); } if (($parentNode = $element->parentNode) !== null) { $parentNode->replaceChild($fragment, $element); } } return; } elseif (in_array($tag, ['script', 'style'])) { foreach ($elements as $element) { if (($parentNode = $element->parentNode) !== null) { $parentNode->removeChild($element); } } return; } else { foreach ($elements as $element) { $fragment = $document->createDocumentFragment(); $number = $element->childNodes->length; for ($i = $number; $i > 0; $i--) { if (($child = $element->childNodes->item(0)) !== null) { $fragment->appendChild($child); } } if (($parentNode = $element->parentNode) !== null) { $parentNode->replaceChild($fragment, $element); } } } } /** * @return void */ protected function strip_attr(string $attrib, DOMXPath $xpath) { $elements = $xpath->query('//*[@' . $attrib . ']'); if ($elements === false) { throw new \SimplePie\Exception(sprintf( '%s(): Possibly malformed expression, check argument #1 ($attrib)', __METHOD__ ), 1); } /** @var \DOMElement $element */ foreach ($elements as $element) { $element->removeAttribute($attrib); } } /** * @return void */ protected function rename_attr(string $attrib, DOMXPath $xpath) { $elements = $xpath->query('//*[@' . $attrib . ']'); if ($elements === false) { throw new \SimplePie\Exception(sprintf( '%s(): Possibly malformed expression, check argument #1 ($attrib)', __METHOD__ ), 1); } /** @var \DOMElement $element */ foreach ($elements as $element) { $element->setAttribute('data-sanitized-' . $attrib, $element->getAttribute($attrib)); $element->removeAttribute($attrib); } } /** * @param array $valuePairs * @return void */ protected function add_attr(string $tag, array $valuePairs, DOMDocument $document) { $elements = $document->getElementsByTagName($tag); /** @var \DOMElement $element */ foreach ($elements as $element) { foreach ($valuePairs as $attrib => $value) { $element->setAttribute($attrib, $value); } } } /** * Get a DataCache * * @param string $image_url Only needed for BC, can be removed in SimplePie 2.0.0 * * @return DataCache */ private function get_cache(string $image_url = ''): DataCache { if ($this->cache === null) { // @trigger_error(sprintf('Not providing as PSR-16 cache implementation is deprecated since SimplePie 1.8.0, please use "SimplePie\SimplePie::set_cache()".'), \E_USER_DEPRECATED); $cache = $this->registry->call(Cache::class, 'get_handler', [ $this->cache_location, $image_url, Base::TYPE_IMAGE ]); return new BaseDataCache($cache); } return $this->cache; } /** * Get a HTTP client */ private function get_http_client(): Client { if ($this->http_client === null) { $this->http_client = new FileClient( $this->registry, [ 'timeout' => $this->timeout, 'redirects' => 5, 'useragent' => $this->useragent, 'force_fsockopen' => $this->force_fsockopen, 'curl_options' => $this->curl_options, ] ); } return $this->http_client; } } class_alias('SimplePie\Sanitize', 'SimplePie_Sanitize'); Author.php000064400000003562152505244670006537 0ustar00name = $name; $this->link = $link; $this->email = $email; } /** * String-ified version * * @return string */ public function __toString() { // There is no $this->data here return md5(serialize($this)); } /** * Author's name * * @return string|null */ public function get_name() { if ($this->name !== null) { return $this->name; } return null; } /** * Author's link * * @return string|null */ public function get_link() { if ($this->link !== null) { return $this->link; } return null; } /** * Author's email address * * @return string|null */ public function get_email() { if ($this->email !== null) { return $this->email; } return null; } } class_alias('SimplePie\Author', 'SimplePie_Author'); Caption.php000064400000006033152505244670006666 0ustar00` captions as defined in Media RSS. * * Used by {@see \SimplePie\Enclosure::get_caption()} and {@see \SimplePie\Enclosure::get_captions()} * * This class can be overloaded with {@see \SimplePie\SimplePie::set_caption_class()} */ class Caption { /** * Content type * * @var ?string * @see get_type() */ public $type; /** * Language * * @var ?string * @see get_language() */ public $lang; /** * Start time * * @var ?string * @see get_starttime() */ public $startTime; /** * End time * * @var ?string * @see get_endtime() */ public $endTime; /** * Caption text * * @var ?string * @see get_text() */ public $text; /** * Constructor, used to input the data * * For documentation on all the parameters, see the corresponding * properties and their accessors */ public function __construct( ?string $type = null, ?string $lang = null, ?string $startTime = null, ?string $endTime = null, ?string $text = null ) { $this->type = $type; $this->lang = $lang; $this->startTime = $startTime; $this->endTime = $endTime; $this->text = $text; } /** * String-ified version * * @return string */ public function __toString() { // There is no $this->data here return md5(serialize($this)); } /** * Get the end time * * @return string|null Time in the format 'hh:mm:ss.SSS' */ public function get_endtime() { if ($this->endTime !== null) { return $this->endTime; } return null; } /** * Get the language * * @link http://tools.ietf.org/html/rfc3066 * @return string|null Language code as per RFC 3066 */ public function get_language() { if ($this->lang !== null) { return $this->lang; } return null; } /** * Get the start time * * @return string|null Time in the format 'hh:mm:ss.SSS' */ public function get_starttime() { if ($this->startTime !== null) { return $this->startTime; } return null; } /** * Get the text of the caption * * @return string|null */ public function get_text() { if ($this->text !== null) { return $this->text; } return null; } /** * Get the content type (not MIME type) * * @return string|null Either 'text' or 'html' */ public function get_type() { if ($this->type !== null) { return $this->type; } return null; } } class_alias('SimplePie\Caption', 'SimplePie_Caption'); HTTP/error_log000064400000070726152505244670007266 0ustar00[03-Apr-2026 19:13:15 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Client" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/FileClient.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/FileClient.php on line 21 [03-Apr-2026 19:13:15 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/RawTextResponse.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/RawTextResponse.php on line 18 [03-Apr-2026 19:13:16 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr7Response.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr7Response.php on line 20 [03-Apr-2026 19:13:16 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Client" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr18Client.php:22 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr18Client.php on line 22 [03-Apr-2026 19:13:17 UTC] PHP Fatal error: Uncaught Error: Class "SimplePie\Exception" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/ClientException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/ClientException.php on line 17 [05-Apr-2026 16:35:26 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Client" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr18Client.php:22 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr18Client.php on line 22 [05-Apr-2026 16:35:26 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/RawTextResponse.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/RawTextResponse.php on line 18 [05-Apr-2026 16:35:26 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Client" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/FileClient.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/FileClient.php on line 21 [05-Apr-2026 16:35:27 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr7Response.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr7Response.php on line 20 [05-Apr-2026 16:35:27 UTC] PHP Fatal error: Uncaught Error: Class "SimplePie\Exception" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/ClientException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/ClientException.php on line 17 [08-Apr-2026 14:31:50 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Client" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/FileClient.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/FileClient.php on line 21 [08-Apr-2026 14:31:50 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr7Response.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr7Response.php on line 20 [08-Apr-2026 14:31:50 UTC] PHP Fatal error: Uncaught Error: Class "SimplePie\Exception" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/ClientException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/ClientException.php on line 17 [08-Apr-2026 14:31:51 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Client" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr18Client.php:22 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr18Client.php on line 22 [08-Apr-2026 14:31:51 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/RawTextResponse.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/RawTextResponse.php on line 18 [15-Jul-2026 16:18:28 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr7Response.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr7Response.php on line 20 [15-Jul-2026 16:18:29 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Client" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr18Client.php:22 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr18Client.php on line 22 [15-Jul-2026 16:18:29 UTC] PHP Fatal error: Uncaught Error: Class "SimplePie\Exception" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/ClientException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/ClientException.php on line 17 [15-Jul-2026 16:18:29 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/RawTextResponse.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/RawTextResponse.php on line 18 [15-Jul-2026 16:18:29 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Client" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/FileClient.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/FileClient.php on line 21 [24-Jul-2026 12:14:27 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Client" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/FileClient.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/FileClient.php on line 21 [24-Jul-2026 12:14:27 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/RawTextResponse.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/RawTextResponse.php on line 18 [24-Jul-2026 12:14:28 UTC] PHP Fatal error: Uncaught Error: Class "SimplePie\Exception" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/ClientException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/ClientException.php on line 17 [24-Jul-2026 12:14:28 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr7Response.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr7Response.php on line 20 [24-Jul-2026 12:14:28 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Client" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr18Client.php:22 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr18Client.php on line 22 [25-Jul-2026 05:34:50 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr7Response.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr7Response.php on line 20 [25-Jul-2026 05:34:51 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Client" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr18Client.php:22 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr18Client.php on line 22 [25-Jul-2026 05:34:51 UTC] PHP Fatal error: Uncaught Error: Class "SimplePie\Exception" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/ClientException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/ClientException.php on line 17 [25-Jul-2026 05:34:52 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Client" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/FileClient.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/FileClient.php on line 21 [25-Jul-2026 05:34:52 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/RawTextResponse.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/RawTextResponse.php on line 18 [04-Aug-2026 23:31:32 UTC] PHP Fatal error: Uncaught Error: Class "SimplePie\Exception" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/ClientException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/ClientException.php on line 17 [04-Aug-2026 23:31:33 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr7Response.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr7Response.php on line 20 [04-Aug-2026 23:31:33 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/RawTextResponse.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/RawTextResponse.php on line 18 [04-Aug-2026 23:31:34 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Client" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/FileClient.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/FileClient.php on line 21 [04-Aug-2026 23:31:34 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Client" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr18Client.php:22 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr18Client.php on line 22 [13-Aug-2026 01:30:22 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Client" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr18Client.php:22 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr18Client.php on line 22 [13-Aug-2026 01:30:22 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Client" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/FileClient.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/FileClient.php on line 21 [13-Aug-2026 01:30:22 UTC] PHP Fatal error: Uncaught Error: Class "SimplePie\Exception" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/ClientException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/ClientException.php on line 17 [13-Aug-2026 01:30:23 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/RawTextResponse.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/RawTextResponse.php on line 18 [13-Aug-2026 01:30:23 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr7Response.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr7Response.php on line 20 [13-Aug-2026 12:58:39 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr7Response.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr7Response.php on line 20 [13-Aug-2026 12:58:40 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/RawTextResponse.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/RawTextResponse.php on line 18 [13-Aug-2026 12:58:40 UTC] PHP Fatal error: Uncaught Error: Class "SimplePie\Exception" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/ClientException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/ClientException.php on line 17 [13-Aug-2026 12:58:40 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Client" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr18Client.php:22 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr18Client.php on line 22 [13-Aug-2026 12:58:41 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Client" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/FileClient.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/FileClient.php on line 21 [19-Aug-2026 21:32:12 UTC] PHP Fatal error: Uncaught Error: Class "SimplePie\Exception" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/ClientException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/ClientException.php on line 17 [19-Aug-2026 21:32:12 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Client" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/FileClient.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/FileClient.php on line 21 [19-Aug-2026 21:32:12 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/RawTextResponse.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/RawTextResponse.php on line 18 [19-Aug-2026 21:32:12 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Client" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr18Client.php:22 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr18Client.php on line 22 [19-Aug-2026 21:32:12 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr7Response.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr7Response.php on line 20 [19-Aug-2026 22:06:53 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/RawTextResponse.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/RawTextResponse.php on line 18 [19-Aug-2026 22:06:53 UTC] PHP Fatal error: Uncaught Error: Class "SimplePie\Exception" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/ClientException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/ClientException.php on line 17 [19-Aug-2026 22:06:54 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Client" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/FileClient.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/FileClient.php on line 21 [19-Aug-2026 22:06:54 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr7Response.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr7Response.php on line 20 [19-Aug-2026 22:06:54 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Client" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr18Client.php:22 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr18Client.php on line 22 [19-Aug-2026 23:27:25 UTC] PHP Fatal error: Uncaught Error: Class "SimplePie\Exception" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/ClientException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/ClientException.php on line 17 [19-Aug-2026 23:27:26 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/RawTextResponse.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/RawTextResponse.php on line 18 [19-Aug-2026 23:27:26 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr7Response.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr7Response.php on line 20 [19-Aug-2026 23:27:26 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Client" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr18Client.php:22 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr18Client.php on line 22 [19-Aug-2026 23:27:27 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Client" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/FileClient.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/FileClient.php on line 21 [03-Sep-2026 06:31:49 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Client" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr18Client.php:22 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr18Client.php on line 22 [03-Sep-2026 06:32:26 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr7Response.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr7Response.php on line 20 [03-Sep-2026 06:32:43 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/RawTextResponse.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/RawTextResponse.php on line 18 [03-Sep-2026 06:32:44 UTC] PHP Fatal error: Uncaught Error: Class "SimplePie\Exception" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/ClientException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/ClientException.php on line 17 [03-Sep-2026 06:46:16 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Client" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/FileClient.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/FileClient.php on line 21 [03-Sep-2026 08:08:14 UTC] PHP Fatal error: Uncaught Error: Class "SimplePie\Exception" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/ClientException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/ClientException.php on line 17 [03-Sep-2026 08:08:15 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Client" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/FileClient.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/FileClient.php on line 21 [03-Sep-2026 08:08:17 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Client" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr18Client.php:22 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr18Client.php on line 22 [03-Sep-2026 08:08:17 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr7Response.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr7Response.php on line 20 [03-Sep-2026 08:08:18 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/RawTextResponse.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/RawTextResponse.php on line 18 [03-Sep-2026 17:06:16 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Client" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/FileClient.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/FileClient.php on line 21 [03-Sep-2026 17:34:52 UTC] PHP Fatal error: Uncaught Error: Class "SimplePie\Exception" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/ClientException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/ClientException.php on line 17 [03-Sep-2026 18:00:54 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/RawTextResponse.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/RawTextResponse.php on line 18 [03-Sep-2026 18:00:55 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Client" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr18Client.php:22 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr18Client.php on line 22 [03-Sep-2026 18:00:57 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr7Response.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr7Response.php on line 20 [04-Sep-2026 02:18:59 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr7Response.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr7Response.php on line 20 [04-Sep-2026 06:36:51 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Client" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/FileClient.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/FileClient.php on line 21 [04-Sep-2026 06:36:53 UTC] PHP Fatal error: Uncaught Error: Class "SimplePie\Exception" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/ClientException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/ClientException.php on line 17 [04-Sep-2026 09:05:14 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Client" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr18Client.php:22 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr18Client.php on line 22 [04-Sep-2026 13:10:30 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Client" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/FileClient.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/FileClient.php on line 21 [04-Sep-2026 13:10:35 UTC] PHP Fatal error: Uncaught Error: Class "SimplePie\Exception" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/ClientException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/ClientException.php on line 17 [04-Sep-2026 13:18:39 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/RawTextResponse.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/RawTextResponse.php on line 18 [04-Sep-2026 15:45:07 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/RawTextResponse.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/RawTextResponse.php on line 18 [04-Sep-2026 15:45:17 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Client" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr18Client.php:22 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr18Client.php on line 22 [04-Sep-2026 18:51:05 UTC] PHP Fatal error: Uncaught Error: Class "SimplePie\Exception" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/ClientException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/ClientException.php on line 17 [04-Sep-2026 21:07:47 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Client" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/FileClient.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/FileClient.php on line 21 [04-Sep-2026 22:38:18 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/RawTextResponse.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/RawTextResponse.php on line 18 [04-Sep-2026 22:38:21 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Client" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr18Client.php:22 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr18Client.php on line 22 [04-Sep-2026 22:38:25 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr7Response.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr7Response.php on line 20 [05-Sep-2026 07:46:05 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\HTTP\Response" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr7Response.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/HTTP/Psr7Response.php on line 20 HTTP/Response.php000064400000015435152505244670007654 0ustar00get_headers() as $name => $values) { * echo $name . ': ' . implode(', ', $values); * } * * // Emit headers iteratively: * foreach ($message->get_headers() as $name => $values) { * foreach ($values as $value) { * header(sprintf('%s: %s', $name, $value), false); * } * } * * @return array> Returns an associative array of the message's headers. * Each key MUST be a header name, and each value MUST be an array of * strings for that header. */ public function get_headers(): array; /** * Checks if a header exists by the given case-insensitive name. * * @param string $name Case-insensitive header field name. * @return bool Returns true if any header names match the given header * name using a case-insensitive string comparison. Returns false if * no matching header name is found in the message. */ public function has_header(string $name): bool; /** * Retrieves a message header value by the given case-insensitive name. * * This method returns an array of all the header values of the given * case-insensitive header name. * * If the header does not appear in the message, this method MUST return an * empty array. * * @param string $name Case-insensitive header field name. * @return string[] An array of string values as provided for the given * header. If the header does not appear in the message, this method MUST * return an empty array. */ public function get_header(string $name): array; /** * Return an instance with the provided value replacing the specified header. * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return an instance that has the * new and/or updated header and value. * * @param string $name Case-insensitive header field name. * @param string|non-empty-array $value Header value(s). * @return static * @throws \InvalidArgumentException for invalid header names or values. */ public function with_header(string $name, $value); /** * Retrieves a comma-separated string of the values for a single header. * * This method returns all of the header values of the given * case-insensitive header name as a string concatenated together using * a comma. * * NOTE: Not all header values may be appropriately represented using * comma concatenation. For such headers, use getHeader() instead * and supply your own delimiter when concatenating. * * If the header does not appear in the message, this method MUST return * an empty string. * * @param string $name Case-insensitive header field name. * @return string A string of values as provided for the given header * concatenated together using a comma. If the header does not appear in * the message, this method MUST return an empty string. */ public function get_header_line(string $name): string; /** * get the body as string * * @return string */ public function get_body_content(): string; } HTTP/Psr7Response.php000064400000004201152505244670010415 0ustar00response = $response; $this->permanent_url = $permanent_url; $this->requested_url = $requested_url; } public function get_permanent_uri(): string { return $this->permanent_url; } public function get_final_requested_uri(): string { return $this->requested_url; } public function get_status_code(): int { return $this->response->getStatusCode(); } public function get_headers(): array { // The filtering is probably redundant but let’s make PHPStan happy. return array_filter($this->response->getHeaders(), function (array $header): bool { return count($header) >= 1; }); } public function has_header(string $name): bool { return $this->response->hasHeader($name); } public function with_header(string $name, $value) { return new self($this->response->withHeader($name, $value), $this->permanent_url, $this->requested_url); } public function get_header(string $name): array { return $this->response->getHeader($name); } public function get_header_line(string $name): string { return $this->response->getHeaderLine($name); } public function get_body_content(): string { return $this->response->getBody()->__toString(); } } HTTP/Client.php000064400000001137152505244670007266 0ustar00 $headers * * @throws ClientException if anything goes wrong requesting the data */ public function request(string $method, string $url, array $headers = []): Response; } HTTP/RawTextResponse.php000064400000004027152505244670011166 0ustar00> */ private $headers = []; /** * @var string */ private $requested_url; public function __construct(string $raw_text, string $filepath) { $this->raw_text = $raw_text; $this->permanent_url = $filepath; $this->requested_url = $filepath; } public function get_permanent_uri(): string { return $this->permanent_url; } public function get_final_requested_uri(): string { return $this->requested_url; } public function get_status_code(): int { return 200; } public function get_headers(): array { return $this->headers; } public function has_header(string $name): bool { return isset($this->headers[strtolower($name)]); } public function get_header(string $name): array { return isset($this->headers[strtolower($name)]) ? $this->headers[$name] : []; } public function with_header(string $name, $value) { $new = clone $this; $newHeader = [ strtolower($name) => (array) $value, ]; $new->headers = $newHeader + $this->headers; return $new; } public function get_header_line(string $name): string { return isset($this->headers[strtolower($name)]) ? implode(", ", $this->headers[$name]) : ''; } public function get_body_content(): string { return $this->raw_text; } } HTTP/FileClient.php000064400000004333152505244670010067 0ustar00} */ private $options; /** * @param array{timeout?: int, redirects?: int, useragent?: string, force_fsockopen?: bool, curl_options?: array} $options */ public function __construct(Registry $registry, array $options = []) { $this->registry = $registry; $this->options = $options; } /** * send a request and return the response * * @param Client::METHOD_* $method * @param array $headers * * @throws ClientException if anything goes wrong requesting the data */ public function request(string $method, string $url, array $headers = []): Response { // @phpstan-ignore-next-line Enforce PHPDoc type. if ($method !== self::METHOD_GET) { throw new InvalidArgumentException(sprintf( '%s(): Argument #1 ($method) only supports method "%s".', __METHOD__, self::METHOD_GET ), 1); } try { $file = $this->registry->create(File::class, [ $url, $this->options['timeout'] ?? 10, $this->options['redirects'] ?? 5, $headers, $this->options['useragent'] ?? Misc::get_default_useragent(), $this->options['force_fsockopen'] ?? false, $this->options['curl_options'] ?? [] ]); } catch (Throwable $th) { throw new ClientException($th->getMessage(), $th->getCode(), $th); } if ($file->error !== null && $file->get_status_code() === 0) { throw new ClientException($file->error); } return $file; } } HTTP/Psr18Client.php000064400000010537152505244670010130 0ustar00httpClient = $httpClient; $this->requestFactory = $requestFactory; $this->uriFactory = $uriFactory; } public function getHttpClient(): ClientInterface { return $this->httpClient; } public function getRequestFactory(): RequestFactoryInterface { return $this->requestFactory; } public function getUriFactory(): UriFactoryInterface { return $this->uriFactory; } /** * send a request and return the response * * @param Client::METHOD_* $method * @param string $url * @param array $headers * * @throws ClientException if anything goes wrong requesting the data */ public function request(string $method, string $url, array $headers = []): Response { if ($method !== self::METHOD_GET) { throw new InvalidArgumentException(sprintf( '%s(): Argument #1 ($method) only supports method "%s".', __METHOD__, self::METHOD_GET ), 1); } if (preg_match('/^http(s)?:\/\//i', $url)) { return $this->requestUrl($method, $url, $headers); } return $this->requestLocalFile($url); } /** * @param array $headers */ private function requestUrl(string $method, string $url, array $headers): Response { $permanentUrl = $url; $requestedUrl = $url; $remainingRedirects = $this->allowedRedirects; $request = $this->requestFactory->createRequest( $method, $this->uriFactory->createUri($requestedUrl) ); foreach ($headers as $name => $value) { $request = $request->withHeader($name, $value); } do { $followRedirect = false; try { $response = $this->httpClient->sendRequest($request); } catch (ClientExceptionInterface $th) { throw new ClientException($th->getMessage(), $th->getCode(), $th); } $statusCode = $response->getStatusCode(); // If we have a redirect if (in_array($statusCode, [300, 301, 302, 303, 307]) && $response->hasHeader('Location')) { // Prevent infinity redirect loops if ($remainingRedirects <= 0) { break; } $remainingRedirects--; $followRedirect = true; $requestedUrl = $response->getHeaderLine('Location'); if ($statusCode === 301) { $permanentUrl = $requestedUrl; } $request = $request->withUri($this->uriFactory->createUri($requestedUrl)); } } while ($followRedirect); return new Psr7Response($response, $permanentUrl, $requestedUrl); } private function requestLocalFile(string $path): Response { if (!is_readable($path)) { throw new ClientException(sprintf('file "%s" is not readable', $path)); } try { $raw = file_get_contents($path); } catch (Throwable $th) { throw new ClientException($th->getMessage(), $th->getCode(), $th); } if ($raw === false) { throw new ClientException('file_get_contents() could not read the file', 1); } return new RawTextResponse($raw, $path); } } HTTP/Parser.php000064400000035551152505244670007313 0ustar00> : array) */ public $headers = []; /** * Body of the response * * @var string */ public $body = ''; private const STATE_HTTP_VERSION = 'http_version'; private const STATE_STATUS = 'status'; private const STATE_REASON = 'reason'; private const STATE_NEW_LINE = 'new_line'; private const STATE_BODY = 'body'; private const STATE_NAME = 'name'; private const STATE_VALUE = 'value'; private const STATE_VALUE_CHAR = 'value_char'; private const STATE_QUOTE = 'quote'; private const STATE_QUOTE_ESCAPED = 'quote_escaped'; private const STATE_QUOTE_CHAR = 'quote_char'; private const STATE_CHUNKED = 'chunked'; private const STATE_EMIT = 'emit'; private const STATE_ERROR = false; /** * Current state of the state machine * * @var self::STATE_* */ protected $state = self::STATE_HTTP_VERSION; /** * Input data * * @var string */ protected $data = ''; /** * Input data length (to avoid calling strlen() everytime this is needed) * * @var int */ protected $data_length = 0; /** * Current position of the pointer * * @var int */ protected $position = 0; /** * Name of the header currently being parsed * * @var string */ protected $name = ''; /** * Value of the header currently being parsed * * @var string */ protected $value = ''; /** * Create an instance of the class with the input data * * @param string $data Input data * @param Psr7Compatible $psr7Compatible Whether the data types are in format compatible with PSR-7. */ public function __construct(string $data, bool $psr7Compatible = false) { $this->data = $data; $this->data_length = strlen($this->data); $this->psr7Compatible = $psr7Compatible; } /** * Parse the input data * * @return bool true on success, false on failure */ public function parse() { while ($this->state && $this->state !== self::STATE_EMIT && $this->has_data()) { $state = $this->state; $this->$state(); } $this->data = ''; if ($this->state === self::STATE_EMIT || $this->state === self::STATE_BODY) { return true; } // Reset the parser state. $this->http_version = 0.0; $this->status_code = 0; $this->reason = ''; $this->headers = []; $this->body = ''; return false; } /** * Check whether there is data beyond the pointer * * @return bool true if there is further data, false if not */ protected function has_data() { return (bool) ($this->position < $this->data_length); } /** * See if the next character is LWS * * @return bool true if the next character is LWS, false if not */ protected function is_linear_whitespace() { return (bool) ($this->data[$this->position] === "\x09" || $this->data[$this->position] === "\x20" || ($this->data[$this->position] === "\x0A" && isset($this->data[$this->position + 1]) && ($this->data[$this->position + 1] === "\x09" || $this->data[$this->position + 1] === "\x20"))); } /** * Parse the HTTP version * @return void */ protected function http_version() { if (strpos($this->data, "\x0A") !== false && strtoupper(substr($this->data, 0, 5)) === 'HTTP/') { $len = strspn($this->data, '0123456789.', 5); $http_version = substr($this->data, 5, $len); $this->position += 5 + $len; if (substr_count($http_version, '.') <= 1) { $this->http_version = (float) $http_version; $this->position += strspn($this->data, "\x09\x20", $this->position); $this->state = self::STATE_STATUS; } else { $this->state = self::STATE_ERROR; } } else { $this->state = self::STATE_ERROR; } } /** * Parse the status code * @return void */ protected function status() { if ($len = strspn($this->data, '0123456789', $this->position)) { $this->status_code = (int) substr($this->data, $this->position, $len); $this->position += $len; $this->state = self::STATE_REASON; } else { $this->state = self::STATE_ERROR; } } /** * Parse the reason phrase * @return void */ protected function reason() { $len = strcspn($this->data, "\x0A", $this->position); $this->reason = trim(substr($this->data, $this->position, $len), "\x09\x0D\x20"); $this->position += $len + 1; $this->state = self::STATE_NEW_LINE; } private function add_header(string $name, string $value): void { if ($this->psr7Compatible) { // For PHPStan: should be enforced by template parameter but PHPStan is not smart enough. /** @var array> */ $headers = &$this->headers; $headers[$name][] = $value; } else { // For PHPStan: should be enforced by template parameter but PHPStan is not smart enough. /** @var array) */ $headers = &$this->headers; $headers[$name] .= ', ' . $value; } } private function replace_header(string $name, string $value): void { if ($this->psr7Compatible) { // For PHPStan: should be enforced by template parameter but PHPStan is not smart enough. /** @var array> */ $headers = &$this->headers; $headers[$name] = [$value]; } else { // For PHPStan: should be enforced by template parameter but PHPStan is not smart enough. /** @var array) */ $headers = &$this->headers; $headers[$name] = $value; } } /** * Deal with a new line, shifting data around as needed * @return void */ protected function new_line() { $this->value = trim($this->value, "\x0D\x20"); if ($this->name !== '' && $this->value !== '') { $this->name = strtolower($this->name); // We should only use the last Content-Type header. c.f. issue #1 if (isset($this->headers[$this->name]) && $this->name !== 'content-type') { $this->add_header($this->name, $this->value); } else { $this->replace_header($this->name, $this->value); } } $this->name = ''; $this->value = ''; if (substr($this->data[$this->position], 0, 2) === "\x0D\x0A") { $this->position += 2; $this->state = self::STATE_BODY; } elseif ($this->data[$this->position] === "\x0A") { $this->position++; $this->state = self::STATE_BODY; } else { $this->state = self::STATE_NAME; } } /** * Parse a header name * @return void */ protected function name() { $len = strcspn($this->data, "\x0A:", $this->position); if (isset($this->data[$this->position + $len])) { if ($this->data[$this->position + $len] === "\x0A") { $this->position += $len; $this->state = self::STATE_NEW_LINE; } else { $this->name = substr($this->data, $this->position, $len); $this->position += $len + 1; $this->state = self::STATE_VALUE; } } else { $this->state = self::STATE_ERROR; } } /** * Parse LWS, replacing consecutive LWS characters with a single space * @return void */ protected function linear_whitespace() { do { if (substr($this->data, $this->position, 2) === "\x0D\x0A") { $this->position += 2; } elseif ($this->data[$this->position] === "\x0A") { $this->position++; } $this->position += strspn($this->data, "\x09\x20", $this->position); } while ($this->has_data() && $this->is_linear_whitespace()); $this->value .= "\x20"; } /** * See what state to move to while within non-quoted header values * @return void */ protected function value() { if ($this->is_linear_whitespace()) { $this->linear_whitespace(); } else { switch ($this->data[$this->position]) { case '"': // Workaround for ETags: we have to include the quotes as // part of the tag. if (strtolower($this->name) === 'etag') { $this->value .= '"'; $this->position++; $this->state = self::STATE_VALUE_CHAR; break; } $this->position++; $this->state = self::STATE_QUOTE; break; case "\x0A": $this->position++; $this->state = self::STATE_NEW_LINE; break; default: $this->state = self::STATE_VALUE_CHAR; break; } } } /** * Parse a header value while outside quotes * @return void */ protected function value_char() { $len = strcspn($this->data, "\x09\x20\x0A\"", $this->position); $this->value .= substr($this->data, $this->position, $len); $this->position += $len; $this->state = self::STATE_VALUE; } /** * See what state to move to while within quoted header values * @return void */ protected function quote() { if ($this->is_linear_whitespace()) { $this->linear_whitespace(); } else { switch ($this->data[$this->position]) { case '"': $this->position++; $this->state = self::STATE_VALUE; break; case "\x0A": $this->position++; $this->state = self::STATE_NEW_LINE; break; case '\\': $this->position++; $this->state = self::STATE_QUOTE_ESCAPED; break; default: $this->state = self::STATE_QUOTE_CHAR; break; } } } /** * Parse a header value while within quotes * @return void */ protected function quote_char() { $len = strcspn($this->data, "\x09\x20\x0A\"\\", $this->position); $this->value .= substr($this->data, $this->position, $len); $this->position += $len; $this->state = self::STATE_VALUE; } /** * Parse an escaped character within quotes * @return void */ protected function quote_escaped() { $this->value .= $this->data[$this->position]; $this->position++; $this->state = self::STATE_QUOTE; } /** * Parse the body * @return void */ protected function body() { $this->body = substr($this->data, $this->position); if (!empty($this->headers['transfer-encoding'])) { unset($this->headers['transfer-encoding']); $this->state = self::STATE_CHUNKED; } else { $this->state = self::STATE_EMIT; } } /** * Parsed a "Transfer-Encoding: chunked" body * @return void */ protected function chunked() { if (!preg_match('/^([0-9a-f]+)[^\r\n]*\r\n/i', trim($this->body))) { $this->state = self::STATE_EMIT; return; } $decoded = ''; $encoded = $this->body; while (true) { $is_chunked = (bool) preg_match('/^([0-9a-f]+)[^\r\n]*\r\n/i', $encoded, $matches); if (!$is_chunked) { // Looks like it's not chunked after all $this->state = self::STATE_EMIT; return; } $length = hexdec(trim($matches[1])); // For PHPStan: this will only be float when larger than PHP_INT_MAX. // But even on 32-bit systems, it would mean 2GiB chunk, which sounds unlikely. \assert(\is_int($length), "Length needs to be shorter than PHP_INT_MAX"); if ($length === 0) { // Ignore trailer headers $this->state = self::STATE_EMIT; $this->body = $decoded; return; } $chunk_length = strlen($matches[0]); $decoded .= substr($encoded, $chunk_length, $length); $encoded = substr($encoded, $chunk_length + $length + 2); // BC for PHP < 8.0: substr() can return bool instead of string $encoded = ($encoded === false) ? '' : $encoded; if (trim($encoded) === '0' || empty($encoded)) { $this->state = self::STATE_EMIT; $this->body = $decoded; return; } } } /** * Prepare headers (take care of proxies headers) * * @param string $headers Raw headers * @param non-negative-int $count Redirection count. Default to 1. * * @return string */ public static function prepareHeaders(string $headers, int $count = 1) { $data = explode("\r\n\r\n", $headers, $count); $data = array_pop($data); if (false !== stripos($data, "HTTP/1.0 200 Connection established\r\n")) { $exploded = explode("\r\n\r\n", $data, 2); $data = end($exploded); } if (false !== stripos($data, "HTTP/1.1 200 Connection established\r\n")) { $exploded = explode("\r\n\r\n", $data, 2); $data = end($exploded); } return $data; } } class_alias('SimplePie\HTTP\Parser', 'SimplePie_HTTP_Parser'); HTTP/ClientException.php000064400000000515152505244670011144 0ustar00 */ public $cached_entities = []; /** @var string */ public $http_base; /** @var string */ public $base; /** @var int */ public $base_location = 0; /** @var int */ public $checked_feeds = 0; /** @var int */ public $max_checked_feeds = 10; /** @var bool */ public $force_fsockopen = false; /** @var array */ public $curl_options = []; /** @var ?\DomDocument */ public $dom; /** @var ?Registry */ protected $registry; /** * @var Client|null */ private $http_client = null; /** * @param array $curl_options */ public function __construct(File $file, int $timeout = 10, ?string $useragent = null, int $max_checked_feeds = 10, bool $force_fsockopen = false, array $curl_options = []) { $this->file = $file; $this->useragent = $useragent; $this->timeout = $timeout; $this->max_checked_feeds = $max_checked_feeds; $this->force_fsockopen = $force_fsockopen; $this->curl_options = $curl_options; $body = $this->file->get_body_content(); if (class_exists('DOMDocument') && $body != '') { $this->dom = new \DOMDocument(); set_error_handler([Misc::class, 'silence_errors']); try { $this->dom->loadHTML($body); } catch (\Throwable $ex) { $this->dom = null; } restore_error_handler(); } else { $this->dom = null; } } /** * Set a PSR-18 client and PSR-17 factories * * Allows you to use your own HTTP client implementations. */ final public function set_http_client( ClientInterface $http_client, RequestFactoryInterface $request_factory, UriFactoryInterface $uri_factory ): void { $this->http_client = new Psr18Client($http_client, $request_factory, $uri_factory); } /** * @return void */ public function set_registry(\SimplePie\Registry $registry) { $this->registry = $registry; } /** * @param SimplePie::LOCATOR_* $type * @param array|null $working * @return Response|null */ public function find(int $type = \SimplePie\SimplePie::LOCATOR_ALL, ?array &$working = null) { assert($this->registry !== null); if ($this->is_feed($this->file)) { return $this->file; } if (Misc::is_remote_uri($this->file->get_final_requested_uri())) { $sniffer = $this->registry->create(Content\Type\Sniffer::class, [$this->file]); if ($sniffer->get_type() !== 'text/html') { return null; } } if ($type & ~\SimplePie\SimplePie::LOCATOR_NONE) { $this->get_base(); } if ($type & \SimplePie\SimplePie::LOCATOR_AUTODISCOVERY && $working = $this->autodiscovery()) { return $working[0]; } if ($type & (\SimplePie\SimplePie::LOCATOR_LOCAL_EXTENSION | \SimplePie\SimplePie::LOCATOR_LOCAL_BODY | \SimplePie\SimplePie::LOCATOR_REMOTE_EXTENSION | \SimplePie\SimplePie::LOCATOR_REMOTE_BODY) && $this->get_links()) { if ($type & \SimplePie\SimplePie::LOCATOR_LOCAL_EXTENSION && $working = $this->extension($this->local)) { return $working[0]; } if ($type & \SimplePie\SimplePie::LOCATOR_LOCAL_BODY && $working = $this->body($this->local)) { return $working[0]; } if ($type & \SimplePie\SimplePie::LOCATOR_REMOTE_EXTENSION && $working = $this->extension($this->elsewhere)) { return $working[0]; } if ($type & \SimplePie\SimplePie::LOCATOR_REMOTE_BODY && $working = $this->body($this->elsewhere)) { return $working[0]; } } return null; } /** * @return bool */ public function is_feed(Response $file, bool $check_html = false) { assert($this->registry !== null); if (Misc::is_remote_uri($file->get_final_requested_uri())) { $sniffer = $this->registry->create(Content\Type\Sniffer::class, [$file]); $sniffed = $sniffer->get_type(); $mime_types = ['application/rss+xml', 'application/rdf+xml', 'text/rdf', 'application/atom+xml', 'text/xml', 'application/xml', 'application/x-rss+xml']; if ($check_html) { $mime_types[] = 'text/html'; } return in_array($sniffed, $mime_types); } elseif (is_file($file->get_final_requested_uri())) { return true; } else { return false; } } /** * @return void */ public function get_base() { assert($this->registry !== null); if ($this->dom === null) { throw new \SimplePie\Exception('DOMDocument not found, unable to use locator'); } $this->http_base = $this->file->get_final_requested_uri(); $this->base = $this->http_base; $elements = $this->dom->getElementsByTagName('base'); foreach ($elements as $element) { if ($element->hasAttribute('href')) { $base = $this->registry->call(Misc::class, 'absolutize_url', [trim($element->getAttribute('href')), $this->http_base]); if ($base === false) { continue; } $this->base = $base; $this->base_location = method_exists($element, 'getLineNo') ? $element->getLineNo() : 0; break; } } } /** * @return array|null */ public function autodiscovery() { $done = []; $feeds = []; $feeds = array_merge($feeds, $this->search_elements_by_tag('link', $done, $feeds)); $feeds = array_merge($feeds, $this->search_elements_by_tag('a', $done, $feeds)); $feeds = array_merge($feeds, $this->search_elements_by_tag('area', $done, $feeds)); if (!empty($feeds)) { return array_values($feeds); } return null; } /** * @param string[] $done * @param array $feeds * @return array */ protected function search_elements_by_tag(string $name, array &$done, array $feeds) { assert($this->registry !== null); if ($this->dom === null) { throw new \SimplePie\Exception('DOMDocument not found, unable to use locator'); } $links = $this->dom->getElementsByTagName($name); foreach ($links as $link) { if ($this->checked_feeds === $this->max_checked_feeds) { break; } if ($link->hasAttribute('href') && $link->hasAttribute('rel')) { $rel = array_unique($this->registry->call(Misc::class, 'space_separated_tokens', [strtolower($link->getAttribute('rel'))])); $line = method_exists($link, 'getLineNo') ? $link->getLineNo() : 1; if ($this->base_location < $line) { $href = $this->registry->call(Misc::class, 'absolutize_url', [trim($link->getAttribute('href')), $this->base]); } else { $href = $this->registry->call(Misc::class, 'absolutize_url', [trim($link->getAttribute('href')), $this->http_base]); } if ($href === false) { continue; } if (!in_array($href, $done) && in_array('feed', $rel) || (in_array('alternate', $rel) && !in_array('stylesheet', $rel) && $link->hasAttribute('type') && in_array(strtolower($this->registry->call(Misc::class, 'parse_mime', [$link->getAttribute('type')])), ['text/html', 'application/rss+xml', 'application/atom+xml'])) && !isset($feeds[$href])) { $this->checked_feeds++; $headers = [ 'Accept' => SimplePie::DEFAULT_HTTP_ACCEPT_HEADER, ]; try { $feed = $this->get_http_client()->request(Client::METHOD_GET, $href, $headers); if ((!Misc::is_remote_uri($feed->get_final_requested_uri()) || ($feed->get_status_code() === 200 || $feed->get_status_code() > 206 && $feed->get_status_code() < 300)) && $this->is_feed($feed, true)) { $feeds[$href] = $feed; } } catch (ClientException $th) { // Just mark it as done and continue. } } $done[] = $href; } } return $feeds; } /** * @return true|null */ public function get_links() { assert($this->registry !== null); if ($this->dom === null) { throw new \SimplePie\Exception('DOMDocument not found, unable to use locator'); } $links = $this->dom->getElementsByTagName('a'); foreach ($links as $link) { if ($link->hasAttribute('href')) { $href = trim($link->getAttribute('href')); $parsed = $this->registry->call(Misc::class, 'parse_url', [$href]); if ($parsed['scheme'] === '' || preg_match('/^(https?|feed)?$/i', $parsed['scheme'])) { if (method_exists($link, 'getLineNo') && $this->base_location < $link->getLineNo()) { $href = $this->registry->call(Misc::class, 'absolutize_url', [trim($link->getAttribute('href')), $this->base]); } else { $href = $this->registry->call(Misc::class, 'absolutize_url', [trim($link->getAttribute('href')), $this->http_base]); } if ($href === false) { continue; } $current = $this->registry->call(Misc::class, 'parse_url', [$this->file->get_final_requested_uri()]); if ($parsed['authority'] === '' || $parsed['authority'] === $current['authority']) { $this->local[] = $href; } else { $this->elsewhere[] = $href; } } } } $this->local = array_unique($this->local); $this->elsewhere = array_unique($this->elsewhere); if (!empty($this->local) || !empty($this->elsewhere)) { return true; } return null; } /** * Extracts first `link` element with given `rel` attribute inside the `head` element. * * @return string|null */ public function get_rel_link(string $rel) { assert($this->registry !== null); if ($this->dom === null) { throw new \SimplePie\Exception('DOMDocument not found, unable to use '. 'locator'); } if (!class_exists('DOMXpath')) { throw new \SimplePie\Exception('DOMXpath not found, unable to use '. 'get_rel_link'); } $xpath = new \DOMXpath($this->dom); $query = '(//head)[1]/link[@rel and @href]'; /** @var \DOMNodeList<\DOMElement> */ $queryResult = $xpath->query($query); foreach ($queryResult as $link) { $href = trim($link->getAttribute('href')); $parsed = $this->registry->call(Misc::class, 'parse_url', [$href]); if ($parsed['scheme'] === '' || preg_match('/^https?$/i', $parsed['scheme'])) { if (method_exists($link, 'getLineNo') && $this->base_location < $link->getLineNo()) { $href = $this->registry->call( Misc::class, 'absolutize_url', [trim($link->getAttribute('href')), $this->base] ); } else { $href = $this->registry->call( Misc::class, 'absolutize_url', [trim($link->getAttribute('href')), $this->http_base] ); } if ($href === false) { return null; } $rel_values = explode(' ', strtolower($link->getAttribute('rel'))); if (in_array($rel, $rel_values)) { return $href; } } } return null; } /** * @param string[] $array * @return array|null */ public function extension(array &$array) { foreach ($array as $key => $value) { if ($this->checked_feeds === $this->max_checked_feeds) { break; } $extension = strrchr($value, '.'); if ($extension !== false && in_array(strtolower($extension), ['.rss', '.rdf', '.atom', '.xml'])) { $this->checked_feeds++; $headers = [ 'Accept' => SimplePie::DEFAULT_HTTP_ACCEPT_HEADER, ]; try { $feed = $this->get_http_client()->request(Client::METHOD_GET, $value, $headers); if ((!Misc::is_remote_uri($feed->get_final_requested_uri()) || ($feed->get_status_code() === 200 || $feed->get_status_code() > 206 && $feed->get_status_code() < 300)) && $this->is_feed($feed)) { return [$feed]; } } catch (ClientException $th) { // Just unset and continue. } unset($array[$key]); } } return null; } /** * @param string[] $array * @return array|null */ public function body(array &$array) { foreach ($array as $key => $value) { if ($this->checked_feeds === $this->max_checked_feeds) { break; } if (preg_match('/(feed|rss|rdf|atom|xml)/i', $value)) { $this->checked_feeds++; $headers = [ 'Accept' => SimplePie::DEFAULT_HTTP_ACCEPT_HEADER, ]; try { $feed = $this->get_http_client()->request(Client::METHOD_GET, $value, $headers); if ((!Misc::is_remote_uri($feed->get_final_requested_uri()) || ($feed->get_status_code() === 200 || $feed->get_status_code() > 206 && $feed->get_status_code() < 300)) && $this->is_feed($feed)) { return [$feed]; } } catch (ClientException $th) { // Just unset and continue. } unset($array[$key]); } } return null; } /** * Get a HTTP client */ private function get_http_client(): Client { assert($this->registry !== null); if ($this->http_client === null) { $options = [ 'timeout' => $this->timeout, 'redirects' => 5, 'force_fsockopen' => $this->force_fsockopen, 'curl_options' => $this->curl_options, ]; if ($this->useragent !== null) { $options['useragent'] = $this->useragent; } return new FileClient( $this->registry, $options ); } return $this->http_client; } } class_alias('SimplePie\Locator', 'SimplePie_Locator', false); Rating.php000064400000003402152505244670006512 0ustar00` or `` tags as defined in Media RSS and iTunes RSS respectively * * Used by {@see \SimplePie\Enclosure::get_rating()} and {@see \SimplePie\Enclosure::get_ratings()} * * This class can be overloaded with {@see \SimplePie\SimplePie::set_rating_class()} */ class Rating { /** * Rating scheme * * @var ?string * @see get_scheme() */ public $scheme; /** * Rating value * * @var ?string * @see get_value() */ public $value; /** * Constructor, used to input the data * * For documentation on all the parameters, see the corresponding * properties and their accessors */ public function __construct( ?string $scheme = null, ?string $value = null ) { $this->scheme = $scheme; $this->value = $value; } /** * String-ified version * * @return string */ public function __toString() { // There is no $this->data here return md5(serialize($this)); } /** * Get the organizational scheme for the rating * * @return string|null */ public function get_scheme() { if ($this->scheme !== null) { return $this->scheme; } return null; } /** * Get the value of the rating * * @return string|null */ public function get_value() { if ($this->value !== null) { return $this->value; } return null; } } class_alias('SimplePie\Rating', 'SimplePie_Rating'); Misc.php000064400000210245152505244670006166 0ustar00 0) { $time .= $hours.':'; } $minutes = floor($remainder / 60); $seconds = $remainder % 60; if ($minutes < 10 && $hours > 0) { $minutes = '0' . $minutes; } if ($seconds < 10) { $seconds = '0' . $seconds; } $time .= $minutes.':'; $time .= $seconds; return $time; } /** * @return string|false */ public static function absolutize_url(string $relative, string $base) { $iri = \SimplePie\IRI::absolutize(new \SimplePie\IRI($base), $relative); if ($iri === false) { return false; } return $iri->get_uri(); } /** * @internal */ public static function is_remote_uri(string $uri): bool { return preg_match('/^https?:\/\//i', $uri) === 1; } /** * Get a HTML/XML element from a HTML string * * @deprecated since SimplePie 1.3, use DOMDocument instead (parsing HTML with regex is bad!) * @param string $realname Element name (including namespace prefix if applicable) * @param string $string HTML document * @return array, content?: string}> */ public static function get_element(string $realname, string $string) { // trigger_error(sprintf('Using method "' . __METHOD__ . '" is deprecated since SimplePie 1.3, use "DOMDocument" instead.'), \E_USER_DEPRECATED); $return = []; $name = preg_quote($realname, '/'); if (preg_match_all("/<($name)" . \SimplePie\SimplePie::PCRE_HTML_ATTRIBUTE . "(>(.*)<\/$name>|(\/)?>)/siU", $string, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) { for ($i = 0, $total_matches = count($matches); $i < $total_matches; $i++) { $return[$i]['tag'] = $realname; $return[$i]['full'] = $matches[$i][0][0]; $return[$i]['offset'] = $matches[$i][0][1]; if (strlen($matches[$i][3][0]) <= 2) { $return[$i]['self_closing'] = true; } else { $return[$i]['self_closing'] = false; $return[$i]['content'] = $matches[$i][4][0]; } $return[$i]['attribs'] = []; if (isset($matches[$i][2][0]) && preg_match_all('/[\x09\x0A\x0B\x0C\x0D\x20]+([^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3D\x3E]*)(?:[\x09\x0A\x0B\x0C\x0D\x20]*=[\x09\x0A\x0B\x0C\x0D\x20]*(?:"([^"]*)"|\'([^\']*)\'|([^\x09\x0A\x0B\x0C\x0D\x20\x22\x27\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x3E]*)?))?/', ' ' . $matches[$i][2][0] . ' ', $attribs, PREG_SET_ORDER)) { foreach ($attribs as $attrib) { if (count($attrib) === 2) { $attrib[2] = $attrib[1]; } $return[$i]['attribs'][strtolower($attrib[1])]['data'] = Misc::entities_decode(end($attrib)); } } } } return $return; } /** * @deprecated since SimplePie 1.9.0. If you need it, you can copy the function to your codebase. But you should consider using `DOMDocument` for any DOM wrangling. * @param array{tag: string, self_closing: bool, attribs: array, content: string} $element * @return string */ public static function element_implode(array $element) { // trigger_error(sprintf('Using method "' . __METHOD__ . '" is deprecated since SimplePie 1.9.'), \E_USER_DEPRECATED); $full = "<{$element['tag']}"; foreach ($element['attribs'] as $key => $value) { $key = strtolower($key); $full .= " $key=\"" . htmlspecialchars($value['data'], ENT_COMPAT, 'UTF-8') . '"'; } if ($element['self_closing']) { $full .= ' />'; } else { $full .= ">{$element['content']}"; } return $full; } /** * @param string $message * @param int $level * @param string $file * @param int $line * @return string */ public static function error(string $message, int $level, string $file, int $line) { if ((error_reporting() & $level) > 0) { switch ($level) { case E_USER_ERROR: $note = 'PHP Error'; break; case E_USER_WARNING: $note = 'PHP Warning'; break; case E_USER_NOTICE: $note = 'PHP Notice'; break; default: $note = 'Unknown Error'; break; } $log_error = true; if (!function_exists('error_log')) { $log_error = false; } $log_file = @ini_get('error_log'); if (!empty($log_file) && ('syslog' !== $log_file) && !@is_writable($log_file)) { $log_error = false; } if ($log_error) { @error_log("$note: $message in $file on line $line", 0); } } return $message; } /** * @return string */ public static function fix_protocol(string $url, int $http = 1) { $url = Misc::normalize_url($url); $parsed = Misc::parse_url($url); if ($parsed['scheme'] !== '' && $parsed['scheme'] !== 'http' && $parsed['scheme'] !== 'https') { return Misc::fix_protocol(Misc::compress_parse_url('http', $parsed['authority'], $parsed['path'], $parsed['query'], $parsed['fragment']), $http); } if ($parsed['scheme'] === '' && $parsed['authority'] === '' && !file_exists($url)) { return Misc::fix_protocol(Misc::compress_parse_url('http', $parsed['path'], '', $parsed['query'], $parsed['fragment']), $http); } if ($http === 2 && $parsed['scheme'] !== '') { return "feed:$url"; } elseif ($http === 3 && strtolower($parsed['scheme']) === 'http') { return substr_replace($url, 'podcast', 0, 4); } elseif ($http === 4 && strtolower($parsed['scheme']) === 'http') { return substr_replace($url, 'itpc', 0, 4); } return $url; } /** * @deprecated since SimplePie 1.8.0, use PHP native array_replace_recursive() instead. * @param array $array1 * @param array $array2 * @return array */ public static function array_merge_recursive(array $array1, array $array2) { foreach ($array2 as $key => $value) { if (is_array($value)) { $array1[$key] = Misc::array_merge_recursive($array1[$key], $value); } else { $array1[$key] = $value; } } return $array1; } /** * @return array */ public static function parse_url(string $url) { $iri = new \SimplePie\IRI($url); return [ 'scheme' => (string) $iri->scheme, 'authority' => (string) $iri->authority, 'path' => (string) $iri->path, 'query' => (string) $iri->query, 'fragment' => (string) $iri->fragment ]; } /** * @return string */ public static function compress_parse_url(string $scheme = '', string $authority = '', string $path = '', string $query = '', ?string $fragment = '') { $iri = new \SimplePie\IRI(''); $iri->scheme = $scheme; $iri->authority = $authority; $iri->path = $path; $iri->query = $query; $iri->fragment = $fragment; return $iri->get_uri(); } /** * @return string */ public static function normalize_url(string $url) { $iri = new \SimplePie\IRI($url); return $iri->get_uri(); } /** * @deprecated since SimplePie 1.9.0. This functionality is part of `IRI` – if you need it standalone, consider copying the function to your codebase. * @param array $match * @return string */ public static function percent_encoding_normalization(array $match) { $integer = hexdec($match[1]); if ($integer >= 0x41 && $integer <= 0x5A || $integer >= 0x61 && $integer <= 0x7A || $integer >= 0x30 && $integer <= 0x39 || $integer === 0x2D || $integer === 0x2E || $integer === 0x5F || $integer === 0x7E) { // Cast for PHPStan, the value would only be float when above PHP_INT_MAX, which would not go in this branch. return chr((int) $integer); } return strtoupper($match[0]); } /** * Converts a Windows-1252 encoded string to a UTF-8 encoded string * * @static * @param string $string Windows-1252 encoded string * @return string UTF-8 encoded string */ public static function windows_1252_to_utf8(string $string) { static $convert_table = ["\x80" => "\xE2\x82\xAC", "\x81" => "\xEF\xBF\xBD", "\x82" => "\xE2\x80\x9A", "\x83" => "\xC6\x92", "\x84" => "\xE2\x80\x9E", "\x85" => "\xE2\x80\xA6", "\x86" => "\xE2\x80\xA0", "\x87" => "\xE2\x80\xA1", "\x88" => "\xCB\x86", "\x89" => "\xE2\x80\xB0", "\x8A" => "\xC5\xA0", "\x8B" => "\xE2\x80\xB9", "\x8C" => "\xC5\x92", "\x8D" => "\xEF\xBF\xBD", "\x8E" => "\xC5\xBD", "\x8F" => "\xEF\xBF\xBD", "\x90" => "\xEF\xBF\xBD", "\x91" => "\xE2\x80\x98", "\x92" => "\xE2\x80\x99", "\x93" => "\xE2\x80\x9C", "\x94" => "\xE2\x80\x9D", "\x95" => "\xE2\x80\xA2", "\x96" => "\xE2\x80\x93", "\x97" => "\xE2\x80\x94", "\x98" => "\xCB\x9C", "\x99" => "\xE2\x84\xA2", "\x9A" => "\xC5\xA1", "\x9B" => "\xE2\x80\xBA", "\x9C" => "\xC5\x93", "\x9D" => "\xEF\xBF\xBD", "\x9E" => "\xC5\xBE", "\x9F" => "\xC5\xB8", "\xA0" => "\xC2\xA0", "\xA1" => "\xC2\xA1", "\xA2" => "\xC2\xA2", "\xA3" => "\xC2\xA3", "\xA4" => "\xC2\xA4", "\xA5" => "\xC2\xA5", "\xA6" => "\xC2\xA6", "\xA7" => "\xC2\xA7", "\xA8" => "\xC2\xA8", "\xA9" => "\xC2\xA9", "\xAA" => "\xC2\xAA", "\xAB" => "\xC2\xAB", "\xAC" => "\xC2\xAC", "\xAD" => "\xC2\xAD", "\xAE" => "\xC2\xAE", "\xAF" => "\xC2\xAF", "\xB0" => "\xC2\xB0", "\xB1" => "\xC2\xB1", "\xB2" => "\xC2\xB2", "\xB3" => "\xC2\xB3", "\xB4" => "\xC2\xB4", "\xB5" => "\xC2\xB5", "\xB6" => "\xC2\xB6", "\xB7" => "\xC2\xB7", "\xB8" => "\xC2\xB8", "\xB9" => "\xC2\xB9", "\xBA" => "\xC2\xBA", "\xBB" => "\xC2\xBB", "\xBC" => "\xC2\xBC", "\xBD" => "\xC2\xBD", "\xBE" => "\xC2\xBE", "\xBF" => "\xC2\xBF", "\xC0" => "\xC3\x80", "\xC1" => "\xC3\x81", "\xC2" => "\xC3\x82", "\xC3" => "\xC3\x83", "\xC4" => "\xC3\x84", "\xC5" => "\xC3\x85", "\xC6" => "\xC3\x86", "\xC7" => "\xC3\x87", "\xC8" => "\xC3\x88", "\xC9" => "\xC3\x89", "\xCA" => "\xC3\x8A", "\xCB" => "\xC3\x8B", "\xCC" => "\xC3\x8C", "\xCD" => "\xC3\x8D", "\xCE" => "\xC3\x8E", "\xCF" => "\xC3\x8F", "\xD0" => "\xC3\x90", "\xD1" => "\xC3\x91", "\xD2" => "\xC3\x92", "\xD3" => "\xC3\x93", "\xD4" => "\xC3\x94", "\xD5" => "\xC3\x95", "\xD6" => "\xC3\x96", "\xD7" => "\xC3\x97", "\xD8" => "\xC3\x98", "\xD9" => "\xC3\x99", "\xDA" => "\xC3\x9A", "\xDB" => "\xC3\x9B", "\xDC" => "\xC3\x9C", "\xDD" => "\xC3\x9D", "\xDE" => "\xC3\x9E", "\xDF" => "\xC3\x9F", "\xE0" => "\xC3\xA0", "\xE1" => "\xC3\xA1", "\xE2" => "\xC3\xA2", "\xE3" => "\xC3\xA3", "\xE4" => "\xC3\xA4", "\xE5" => "\xC3\xA5", "\xE6" => "\xC3\xA6", "\xE7" => "\xC3\xA7", "\xE8" => "\xC3\xA8", "\xE9" => "\xC3\xA9", "\xEA" => "\xC3\xAA", "\xEB" => "\xC3\xAB", "\xEC" => "\xC3\xAC", "\xED" => "\xC3\xAD", "\xEE" => "\xC3\xAE", "\xEF" => "\xC3\xAF", "\xF0" => "\xC3\xB0", "\xF1" => "\xC3\xB1", "\xF2" => "\xC3\xB2", "\xF3" => "\xC3\xB3", "\xF4" => "\xC3\xB4", "\xF5" => "\xC3\xB5", "\xF6" => "\xC3\xB6", "\xF7" => "\xC3\xB7", "\xF8" => "\xC3\xB8", "\xF9" => "\xC3\xB9", "\xFA" => "\xC3\xBA", "\xFB" => "\xC3\xBB", "\xFC" => "\xC3\xBC", "\xFD" => "\xC3\xBD", "\xFE" => "\xC3\xBE", "\xFF" => "\xC3\xBF"]; return strtr($string, $convert_table); } /** * Change a string from one encoding to another * * @param string $data Raw data in $input encoding * @param string $input Encoding of $data * @param string $output Encoding you want * @return string|false False if we can't convert it */ public static function change_encoding(string $data, string $input, string $output) { $input = Misc::encoding($input); $output = Misc::encoding($output); // We fail to fail on non US-ASCII bytes if ($input === 'US-ASCII') { static $non_ascii_octets = ''; if (!$non_ascii_octets) { for ($i = 0x80; $i <= 0xFF; $i++) { $non_ascii_octets .= chr($i); } } $data = substr($data, 0, strcspn($data, $non_ascii_octets)); } // This is first, as behaviour of this is completely predictable if ($input === 'windows-1252' && $output === 'UTF-8') { return Misc::windows_1252_to_utf8($data); } // This is second, as behaviour of this varies only with PHP version (the middle part of this expression checks the encoding is supported). elseif (function_exists('mb_convert_encoding') && ($return = Misc::change_encoding_mbstring($data, $input, $output))) { return $return; } // This is third, as behaviour of this varies with OS userland and PHP version elseif (function_exists('iconv') && ($return = Misc::change_encoding_iconv($data, $input, $output))) { return $return; } // This is last, as behaviour of this varies with OS userland and PHP version elseif (class_exists('\UConverter') && ($return = Misc::change_encoding_uconverter($data, $input, $output))) { return $return; } // If we can't do anything, just fail return false; } /** * @return string|false */ protected static function change_encoding_mbstring(string $data, string $input, string $output) { if ($input === 'windows-949') { $input = 'EUC-KR'; } if ($output === 'windows-949') { $output = 'EUC-KR'; } if ($input === 'Windows-31J') { $input = 'SJIS'; } if ($output === 'Windows-31J') { $output = 'SJIS'; } // Check that the encoding is supported if (!in_array($input, mb_list_encodings())) { return false; } if (@mb_convert_encoding("\x80", 'UTF-16BE', $input) === "\x00\x80") { return false; } // Let's do some conversion if ($return = @mb_convert_encoding($data, $output, $input)) { return $return; } return false; } /** * @return string|false */ protected static function change_encoding_iconv(string $data, string $input, string $output) { return @iconv($input, $output, $data); } /** * @return string|false */ protected static function change_encoding_uconverter(string $data, string $input, string $output) { return @\UConverter::transcode($data, $output, $input); } /** * Normalize an encoding name * * This is automatically generated by create.php * * To generate it, run `php create.php` on the command line, and copy the * output to replace this function. * * @param string $charset Character set to standardise * @return string Standardised name */ public static function encoding(string $charset) { // Normalization from UTS #22 // Cast for PHPStan, the regex should not fail. switch (strtolower((string) preg_replace('/(?:[^a-zA-Z0-9]+|([^0-9])0+)/', '\1', $charset))) { case 'adobestandardencoding': case 'csadobestandardencoding': return 'Adobe-Standard-Encoding'; case 'adobesymbolencoding': case 'cshppsmath': return 'Adobe-Symbol-Encoding'; case 'ami1251': case 'amiga1251': return 'Amiga-1251'; case 'ansix31101983': case 'csat5001983': case 'csiso99naplps': case 'isoir99': case 'naplps': return 'ANSI_X3.110-1983'; case 'arabic7': case 'asmo449': case 'csiso89asmo449': case 'iso9036': case 'isoir89': return 'ASMO_449'; case 'big5': case 'csbig5': return 'Big5'; case 'big5hkscs': return 'Big5-HKSCS'; case 'bocu1': case 'csbocu1': return 'BOCU-1'; case 'brf': case 'csbrf': return 'BRF'; case 'bs4730': case 'csiso4unitedkingdom': case 'gb': case 'iso646gb': case 'isoir4': case 'uk': return 'BS_4730'; case 'bsviewdata': case 'csiso47bsviewdata': case 'isoir47': return 'BS_viewdata'; case 'cesu8': case 'cscesu8': return 'CESU-8'; case 'ca': case 'csa71': case 'csaz243419851': case 'csiso121canadian1': case 'iso646ca': case 'isoir121': return 'CSA_Z243.4-1985-1'; case 'csa72': case 'csaz243419852': case 'csiso122canadian2': case 'iso646ca2': case 'isoir122': return 'CSA_Z243.4-1985-2'; case 'csaz24341985gr': case 'csiso123csaz24341985gr': case 'isoir123': return 'CSA_Z243.4-1985-gr'; case 'csiso139csn369103': case 'csn369103': case 'isoir139': return 'CSN_369103'; case 'csdecmcs': case 'dec': case 'decmcs': return 'DEC-MCS'; case 'csiso21german': case 'de': case 'din66003': case 'iso646de': case 'isoir21': return 'DIN_66003'; case 'csdkus': case 'dkus': return 'dk-us'; case 'csiso646danish': case 'dk': case 'ds2089': case 'iso646dk': return 'DS_2089'; case 'csibmebcdicatde': case 'ebcdicatde': return 'EBCDIC-AT-DE'; case 'csebcdicatdea': case 'ebcdicatdea': return 'EBCDIC-AT-DE-A'; case 'csebcdiccafr': case 'ebcdiccafr': return 'EBCDIC-CA-FR'; case 'csebcdicdkno': case 'ebcdicdkno': return 'EBCDIC-DK-NO'; case 'csebcdicdknoa': case 'ebcdicdknoa': return 'EBCDIC-DK-NO-A'; case 'csebcdices': case 'ebcdices': return 'EBCDIC-ES'; case 'csebcdicesa': case 'ebcdicesa': return 'EBCDIC-ES-A'; case 'csebcdicess': case 'ebcdicess': return 'EBCDIC-ES-S'; case 'csebcdicfise': case 'ebcdicfise': return 'EBCDIC-FI-SE'; case 'csebcdicfisea': case 'ebcdicfisea': return 'EBCDIC-FI-SE-A'; case 'csebcdicfr': case 'ebcdicfr': return 'EBCDIC-FR'; case 'csebcdicit': case 'ebcdicit': return 'EBCDIC-IT'; case 'csebcdicpt': case 'ebcdicpt': return 'EBCDIC-PT'; case 'csebcdicuk': case 'ebcdicuk': return 'EBCDIC-UK'; case 'csebcdicus': case 'ebcdicus': return 'EBCDIC-US'; case 'csiso111ecmacyrillic': case 'ecmacyrillic': case 'isoir111': case 'koi8e': return 'ECMA-cyrillic'; case 'csiso17spanish': case 'es': case 'iso646es': case 'isoir17': return 'ES'; case 'csiso85spanish2': case 'es2': case 'iso646es2': case 'isoir85': return 'ES2'; case 'cseucpkdfmtjapanese': case 'eucjp': case 'extendedunixcodepackedformatforjapanese': return 'EUC-JP'; case 'cseucfixwidjapanese': case 'extendedunixcodefixedwidthforjapanese': return 'Extended_UNIX_Code_Fixed_Width_for_Japanese'; case 'gb18030': return 'GB18030'; case 'chinese': case 'cp936': case 'csgb2312': case 'csiso58gb231280': case 'gb2312': case 'gb231280': case 'gbk': case 'isoir58': case 'ms936': case 'windows936': return 'GBK'; case 'cn': case 'csiso57gb1988': case 'gb198880': case 'iso646cn': case 'isoir57': return 'GB_1988-80'; case 'csiso153gost1976874': case 'gost1976874': case 'isoir153': case 'stsev35888': return 'GOST_19768-74'; case 'csiso150': case 'csiso150greekccitt': case 'greekccitt': case 'isoir150': return 'greek-ccitt'; case 'csiso88greek7': case 'greek7': case 'isoir88': return 'greek7'; case 'csiso18greek7old': case 'greek7old': case 'isoir18': return 'greek7-old'; case 'cshpdesktop': case 'hpdesktop': return 'HP-DeskTop'; case 'cshplegal': case 'hplegal': return 'HP-Legal'; case 'cshpmath8': case 'hpmath8': return 'HP-Math8'; case 'cshppifont': case 'hppifont': return 'HP-Pi-font'; case 'cshproman8': case 'hproman8': case 'r8': case 'roman8': return 'hp-roman8'; case 'hzgb2312': return 'HZ-GB-2312'; case 'csibmsymbols': case 'ibmsymbols': return 'IBM-Symbols'; case 'csibmthai': case 'ibmthai': return 'IBM-Thai'; case 'cp37': case 'csibm37': case 'ebcdiccpca': case 'ebcdiccpnl': case 'ebcdiccpus': case 'ebcdiccpwt': case 'ibm37': return 'IBM037'; case 'cp38': case 'csibm38': case 'ebcdicint': case 'ibm38': return 'IBM038'; case 'cp273': case 'csibm273': case 'ibm273': return 'IBM273'; case 'cp274': case 'csibm274': case 'ebcdicbe': case 'ibm274': return 'IBM274'; case 'cp275': case 'csibm275': case 'ebcdicbr': case 'ibm275': return 'IBM275'; case 'csibm277': case 'ebcdiccpdk': case 'ebcdiccpno': case 'ibm277': return 'IBM277'; case 'cp278': case 'csibm278': case 'ebcdiccpfi': case 'ebcdiccpse': case 'ibm278': return 'IBM278'; case 'cp280': case 'csibm280': case 'ebcdiccpit': case 'ibm280': return 'IBM280'; case 'cp281': case 'csibm281': case 'ebcdicjpe': case 'ibm281': return 'IBM281'; case 'cp284': case 'csibm284': case 'ebcdiccpes': case 'ibm284': return 'IBM284'; case 'cp285': case 'csibm285': case 'ebcdiccpgb': case 'ibm285': return 'IBM285'; case 'cp290': case 'csibm290': case 'ebcdicjpkana': case 'ibm290': return 'IBM290'; case 'cp297': case 'csibm297': case 'ebcdiccpfr': case 'ibm297': return 'IBM297'; case 'cp420': case 'csibm420': case 'ebcdiccpar1': case 'ibm420': return 'IBM420'; case 'cp423': case 'csibm423': case 'ebcdiccpgr': case 'ibm423': return 'IBM423'; case 'cp424': case 'csibm424': case 'ebcdiccphe': case 'ibm424': return 'IBM424'; case '437': case 'cp437': case 'cspc8codepage437': case 'ibm437': return 'IBM437'; case 'cp500': case 'csibm500': case 'ebcdiccpbe': case 'ebcdiccpch': case 'ibm500': return 'IBM500'; case 'cp775': case 'cspc775baltic': case 'ibm775': return 'IBM775'; case '850': case 'cp850': case 'cspc850multilingual': case 'ibm850': return 'IBM850'; case '851': case 'cp851': case 'csibm851': case 'ibm851': return 'IBM851'; case '852': case 'cp852': case 'cspcp852': case 'ibm852': return 'IBM852'; case '855': case 'cp855': case 'csibm855': case 'ibm855': return 'IBM855'; case '857': case 'cp857': case 'csibm857': case 'ibm857': return 'IBM857'; case 'ccsid858': case 'cp858': case 'ibm858': case 'pcmultilingual850euro': return 'IBM00858'; case '860': case 'cp860': case 'csibm860': case 'ibm860': return 'IBM860'; case '861': case 'cp861': case 'cpis': case 'csibm861': case 'ibm861': return 'IBM861'; case '862': case 'cp862': case 'cspc862latinhebrew': case 'ibm862': return 'IBM862'; case '863': case 'cp863': case 'csibm863': case 'ibm863': return 'IBM863'; case 'cp864': case 'csibm864': case 'ibm864': return 'IBM864'; case '865': case 'cp865': case 'csibm865': case 'ibm865': return 'IBM865'; case '866': case 'cp866': case 'csibm866': case 'ibm866': return 'IBM866'; case 'cp868': case 'cpar': case 'csibm868': case 'ibm868': return 'IBM868'; case '869': case 'cp869': case 'cpgr': case 'csibm869': case 'ibm869': return 'IBM869'; case 'cp870': case 'csibm870': case 'ebcdiccproece': case 'ebcdiccpyu': case 'ibm870': return 'IBM870'; case 'cp871': case 'csibm871': case 'ebcdiccpis': case 'ibm871': return 'IBM871'; case 'cp880': case 'csibm880': case 'ebcdiccyrillic': case 'ibm880': return 'IBM880'; case 'cp891': case 'csibm891': case 'ibm891': return 'IBM891'; case 'cp903': case 'csibm903': case 'ibm903': return 'IBM903'; case '904': case 'cp904': case 'csibbm904': case 'ibm904': return 'IBM904'; case 'cp905': case 'csibm905': case 'ebcdiccptr': case 'ibm905': return 'IBM905'; case 'cp918': case 'csibm918': case 'ebcdiccpar2': case 'ibm918': return 'IBM918'; case 'ccsid924': case 'cp924': case 'ebcdiclatin9euro': case 'ibm924': return 'IBM00924'; case 'cp1026': case 'csibm1026': case 'ibm1026': return 'IBM1026'; case 'ibm1047': return 'IBM1047'; case 'ccsid1140': case 'cp1140': case 'ebcdicus37euro': case 'ibm1140': return 'IBM01140'; case 'ccsid1141': case 'cp1141': case 'ebcdicde273euro': case 'ibm1141': return 'IBM01141'; case 'ccsid1142': case 'cp1142': case 'ebcdicdk277euro': case 'ebcdicno277euro': case 'ibm1142': return 'IBM01142'; case 'ccsid1143': case 'cp1143': case 'ebcdicfi278euro': case 'ebcdicse278euro': case 'ibm1143': return 'IBM01143'; case 'ccsid1144': case 'cp1144': case 'ebcdicit280euro': case 'ibm1144': return 'IBM01144'; case 'ccsid1145': case 'cp1145': case 'ebcdices284euro': case 'ibm1145': return 'IBM01145'; case 'ccsid1146': case 'cp1146': case 'ebcdicgb285euro': case 'ibm1146': return 'IBM01146'; case 'ccsid1147': case 'cp1147': case 'ebcdicfr297euro': case 'ibm1147': return 'IBM01147'; case 'ccsid1148': case 'cp1148': case 'ebcdicinternational500euro': case 'ibm1148': return 'IBM01148'; case 'ccsid1149': case 'cp1149': case 'ebcdicis871euro': case 'ibm1149': return 'IBM01149'; case 'csiso143iecp271': case 'iecp271': case 'isoir143': return 'IEC_P27-1'; case 'csiso49inis': case 'inis': case 'isoir49': return 'INIS'; case 'csiso50inis8': case 'inis8': case 'isoir50': return 'INIS-8'; case 'csiso51iniscyrillic': case 'iniscyrillic': case 'isoir51': return 'INIS-cyrillic'; case 'csinvariant': case 'invariant': return 'INVARIANT'; case 'iso2022cn': return 'ISO-2022-CN'; case 'iso2022cnext': return 'ISO-2022-CN-EXT'; case 'csiso2022jp': case 'iso2022jp': return 'ISO-2022-JP'; case 'csiso2022jp2': case 'iso2022jp2': return 'ISO-2022-JP-2'; case 'csiso2022kr': case 'iso2022kr': return 'ISO-2022-KR'; case 'cswindows30latin1': case 'iso88591windows30latin1': return 'ISO-8859-1-Windows-3.0-Latin-1'; case 'cswindows31latin1': case 'iso88591windows31latin1': return 'ISO-8859-1-Windows-3.1-Latin-1'; case 'csisolatin2': case 'iso88592': case 'iso885921987': case 'isoir101': case 'l2': case 'latin2': return 'ISO-8859-2'; case 'cswindows31latin2': case 'iso88592windowslatin2': return 'ISO-8859-2-Windows-Latin-2'; case 'csisolatin3': case 'iso88593': case 'iso885931988': case 'isoir109': case 'l3': case 'latin3': return 'ISO-8859-3'; case 'csisolatin4': case 'iso88594': case 'iso885941988': case 'isoir110': case 'l4': case 'latin4': return 'ISO-8859-4'; case 'csisolatincyrillic': case 'cyrillic': case 'iso88595': case 'iso885951988': case 'isoir144': return 'ISO-8859-5'; case 'arabic': case 'asmo708': case 'csisolatinarabic': case 'ecma114': case 'iso88596': case 'iso885961987': case 'isoir127': return 'ISO-8859-6'; case 'csiso88596e': case 'iso88596e': return 'ISO-8859-6-E'; case 'csiso88596i': case 'iso88596i': return 'ISO-8859-6-I'; case 'csisolatingreek': case 'ecma118': case 'elot928': case 'greek': case 'greek8': case 'iso88597': case 'iso885971987': case 'isoir126': return 'ISO-8859-7'; case 'csisolatinhebrew': case 'hebrew': case 'iso88598': case 'iso885981988': case 'isoir138': return 'ISO-8859-8'; case 'csiso88598e': case 'iso88598e': return 'ISO-8859-8-E'; case 'csiso88598i': case 'iso88598i': return 'ISO-8859-8-I'; case 'cswindows31latin5': case 'iso88599windowslatin5': return 'ISO-8859-9-Windows-Latin-5'; case 'csisolatin6': case 'iso885910': case 'iso8859101992': case 'isoir157': case 'l6': case 'latin6': return 'ISO-8859-10'; case 'iso885913': return 'ISO-8859-13'; case 'iso885914': case 'iso8859141998': case 'isoceltic': case 'isoir199': case 'l8': case 'latin8': return 'ISO-8859-14'; case 'iso885915': case 'latin9': return 'ISO-8859-15'; case 'iso885916': case 'iso8859162001': case 'isoir226': case 'l10': case 'latin10': return 'ISO-8859-16'; case 'iso10646j1': return 'ISO-10646-J-1'; case 'csunicode': case 'iso10646ucs2': return 'ISO-10646-UCS-2'; case 'csucs4': case 'iso10646ucs4': return 'ISO-10646-UCS-4'; case 'csunicodeascii': case 'iso10646ucsbasic': return 'ISO-10646-UCS-Basic'; case 'csunicodelatin1': case 'iso10646': case 'iso10646unicodelatin1': return 'ISO-10646-Unicode-Latin1'; case 'csiso10646utf1': case 'iso10646utf1': return 'ISO-10646-UTF-1'; case 'csiso115481': case 'iso115481': case 'isotr115481': return 'ISO-11548-1'; case 'csiso90': case 'isoir90': return 'iso-ir-90'; case 'csunicodeibm1261': case 'isounicodeibm1261': return 'ISO-Unicode-IBM-1261'; case 'csunicodeibm1264': case 'isounicodeibm1264': return 'ISO-Unicode-IBM-1264'; case 'csunicodeibm1265': case 'isounicodeibm1265': return 'ISO-Unicode-IBM-1265'; case 'csunicodeibm1268': case 'isounicodeibm1268': return 'ISO-Unicode-IBM-1268'; case 'csunicodeibm1276': case 'isounicodeibm1276': return 'ISO-Unicode-IBM-1276'; case 'csiso646basic1983': case 'iso646basic1983': case 'ref': return 'ISO_646.basic:1983'; case 'csiso2intlrefversion': case 'irv': case 'iso646irv1983': case 'isoir2': return 'ISO_646.irv:1983'; case 'csiso2033': case 'e13b': case 'iso20331983': case 'isoir98': return 'ISO_2033-1983'; case 'csiso5427cyrillic': case 'iso5427': case 'isoir37': return 'ISO_5427'; case 'iso5427cyrillic1981': case 'iso54271981': case 'isoir54': return 'ISO_5427:1981'; case 'csiso5428greek': case 'iso54281980': case 'isoir55': return 'ISO_5428:1980'; case 'csiso6937add': case 'iso6937225': case 'isoir152': return 'ISO_6937-2-25'; case 'csisotextcomm': case 'iso69372add': case 'isoir142': return 'ISO_6937-2-add'; case 'csiso8859supp': case 'iso8859supp': case 'isoir154': case 'latin125': return 'ISO_8859-supp'; case 'csiso10367box': case 'iso10367box': case 'isoir155': return 'ISO_10367-box'; case 'csiso15italian': case 'iso646it': case 'isoir15': case 'it': return 'IT'; case 'csiso13jisc6220jp': case 'isoir13': case 'jisc62201969': case 'jisc62201969jp': case 'katakana': case 'x2017': return 'JIS_C6220-1969-jp'; case 'csiso14jisc6220ro': case 'iso646jp': case 'isoir14': case 'jisc62201969ro': case 'jp': return 'JIS_C6220-1969-ro'; case 'csiso42jisc62261978': case 'isoir42': case 'jisc62261978': return 'JIS_C6226-1978'; case 'csiso87jisx208': case 'isoir87': case 'jisc62261983': case 'jisx2081983': case 'x208': return 'JIS_C6226-1983'; case 'csiso91jisc62291984a': case 'isoir91': case 'jisc62291984a': case 'jpocra': return 'JIS_C6229-1984-a'; case 'csiso92jisc62991984b': case 'iso646jpocrb': case 'isoir92': case 'jisc62291984b': case 'jpocrb': return 'JIS_C6229-1984-b'; case 'csiso93jis62291984badd': case 'isoir93': case 'jisc62291984badd': case 'jpocrbadd': return 'JIS_C6229-1984-b-add'; case 'csiso94jis62291984hand': case 'isoir94': case 'jisc62291984hand': case 'jpocrhand': return 'JIS_C6229-1984-hand'; case 'csiso95jis62291984handadd': case 'isoir95': case 'jisc62291984handadd': case 'jpocrhandadd': return 'JIS_C6229-1984-hand-add'; case 'csiso96jisc62291984kana': case 'isoir96': case 'jisc62291984kana': return 'JIS_C6229-1984-kana'; case 'csjisencoding': case 'jisencoding': return 'JIS_Encoding'; case 'cshalfwidthkatakana': case 'jisx201': case 'x201': return 'JIS_X0201'; case 'csiso159jisx2121990': case 'isoir159': case 'jisx2121990': case 'x212': return 'JIS_X0212-1990'; case 'csiso141jusib1002': case 'iso646yu': case 'isoir141': case 'js': case 'jusib1002': case 'yu': return 'JUS_I.B1.002'; case 'csiso147macedonian': case 'isoir147': case 'jusib1003mac': case 'macedonian': return 'JUS_I.B1.003-mac'; case 'csiso146serbian': case 'isoir146': case 'jusib1003serb': case 'serbian': return 'JUS_I.B1.003-serb'; case 'koi7switched': return 'KOI7-switched'; case 'cskoi8r': case 'koi8r': return 'KOI8-R'; case 'koi8u': return 'KOI8-U'; case 'csksc5636': case 'iso646kr': case 'ksc5636': return 'KSC5636'; case 'cskz1048': case 'kz1048': case 'rk1048': case 'strk10482002': return 'KZ-1048'; case 'csiso19latingreek': case 'isoir19': case 'latingreek': return 'latin-greek'; case 'csiso27latingreek1': case 'isoir27': case 'latingreek1': return 'Latin-greek-1'; case 'csiso158lap': case 'isoir158': case 'lap': case 'latinlap': return 'latin-lap'; case 'csmacintosh': case 'mac': case 'macintosh': return 'macintosh'; case 'csmicrosoftpublishing': case 'microsoftpublishing': return 'Microsoft-Publishing'; case 'csmnem': case 'mnem': return 'MNEM'; case 'csmnemonic': case 'mnemonic': return 'MNEMONIC'; case 'csiso86hungarian': case 'hu': case 'iso646hu': case 'isoir86': case 'msz77953': return 'MSZ_7795.3'; case 'csnatsdano': case 'isoir91': case 'natsdano': return 'NATS-DANO'; case 'csnatsdanoadd': case 'isoir92': case 'natsdanoadd': return 'NATS-DANO-ADD'; case 'csnatssefi': case 'isoir81': case 'natssefi': return 'NATS-SEFI'; case 'csnatssefiadd': case 'isoir82': case 'natssefiadd': return 'NATS-SEFI-ADD'; case 'csiso151cuba': case 'cuba': case 'iso646cu': case 'isoir151': case 'ncnc1081': return 'NC_NC00-10:81'; case 'csiso69french': case 'fr': case 'iso646fr': case 'isoir69': case 'nfz62010': return 'NF_Z_62-010'; case 'csiso25french': case 'iso646fr1': case 'isoir25': case 'nfz620101973': return 'NF_Z_62-010_(1973)'; case 'csiso60danishnorwegian': case 'csiso60norwegian1': case 'iso646no': case 'isoir60': case 'no': case 'ns45511': return 'NS_4551-1'; case 'csiso61norwegian2': case 'iso646no2': case 'isoir61': case 'no2': case 'ns45512': return 'NS_4551-2'; case 'osdebcdicdf3irv': return 'OSD_EBCDIC_DF03_IRV'; case 'osdebcdicdf41': return 'OSD_EBCDIC_DF04_1'; case 'osdebcdicdf415': return 'OSD_EBCDIC_DF04_15'; case 'cspc8danishnorwegian': case 'pc8danishnorwegian': return 'PC8-Danish-Norwegian'; case 'cspc8turkish': case 'pc8turkish': return 'PC8-Turkish'; case 'csiso16portuguese': case 'iso646pt': case 'isoir16': case 'pt': return 'PT'; case 'csiso84portuguese2': case 'iso646pt2': case 'isoir84': case 'pt2': return 'PT2'; case 'cp154': case 'csptcp154': case 'cyrillicasian': case 'pt154': case 'ptcp154': return 'PTCP154'; case 'scsu': return 'SCSU'; case 'csiso10swedish': case 'fi': case 'iso646fi': case 'iso646se': case 'isoir10': case 'se': case 'sen850200b': return 'SEN_850200_B'; case 'csiso11swedishfornames': case 'iso646se2': case 'isoir11': case 'se2': case 'sen850200c': return 'SEN_850200_C'; case 'csiso102t617bit': case 'isoir102': case 't617bit': return 'T.61-7bit'; case 'csiso103t618bit': case 'isoir103': case 't61': case 't618bit': return 'T.61-8bit'; case 'csiso128t101g2': case 'isoir128': case 't101g2': return 'T.101-G2'; case 'cstscii': case 'tscii': return 'TSCII'; case 'csunicode11': case 'unicode11': return 'UNICODE-1-1'; case 'csunicode11utf7': case 'unicode11utf7': return 'UNICODE-1-1-UTF-7'; case 'csunknown8bit': case 'unknown8bit': return 'UNKNOWN-8BIT'; case 'ansix341968': case 'ansix341986': case 'ascii': case 'cp367': case 'csascii': case 'ibm367': case 'iso646irv1991': case 'iso646us': case 'isoir6': case 'us': case 'usascii': return 'US-ASCII'; case 'csusdk': case 'usdk': return 'us-dk'; case 'utf7': return 'UTF-7'; case 'utf8': return 'UTF-8'; case 'utf16': return 'UTF-16'; case 'utf16be': return 'UTF-16BE'; case 'utf16le': return 'UTF-16LE'; case 'utf32': return 'UTF-32'; case 'utf32be': return 'UTF-32BE'; case 'utf32le': return 'UTF-32LE'; case 'csventurainternational': case 'venturainternational': return 'Ventura-International'; case 'csventuramath': case 'venturamath': return 'Ventura-Math'; case 'csventuraus': case 'venturaus': return 'Ventura-US'; case 'csiso70videotexsupp1': case 'isoir70': case 'videotexsuppl': return 'videotex-suppl'; case 'csviqr': case 'viqr': return 'VIQR'; case 'csviscii': case 'viscii': return 'VISCII'; case 'csshiftjis': case 'cswindows31j': case 'mskanji': case 'shiftjis': case 'windows31j': return 'Windows-31J'; case 'iso885911': case 'tis620': return 'windows-874'; case 'cseuckr': case 'csksc56011987': case 'euckr': case 'isoir149': case 'korean': case 'ksc5601': case 'ksc56011987': case 'ksc56011989': case 'windows949': return 'windows-949'; case 'windows1250': return 'windows-1250'; case 'windows1251': return 'windows-1251'; case 'cp819': case 'csisolatin1': case 'ibm819': case 'iso88591': case 'iso885911987': case 'isoir100': case 'l1': case 'latin1': case 'windows1252': return 'windows-1252'; case 'windows1253': return 'windows-1253'; case 'csisolatin5': case 'iso88599': case 'iso885991989': case 'isoir148': case 'l5': case 'latin5': case 'windows1254': return 'windows-1254'; case 'windows1255': return 'windows-1255'; case 'windows1256': return 'windows-1256'; case 'windows1257': return 'windows-1257'; case 'windows1258': return 'windows-1258'; default: return $charset; } } /** * @return string */ public static function get_curl_version() { if (is_array($curl = curl_version())) { $curl = $curl['version']; } else { $curl = '0'; } return $curl; } /** * Strip HTML comments * * @deprecated since SimplePie 1.9.0. If you need it, you can copy the function to your codebase. But you should consider using `DOMDocument` for any DOM wrangling. * @param string $data Data to strip comments from * @return string Comment stripped string */ public static function strip_comments(string $data) { // trigger_error(sprintf('Using method "' . __METHOD__ . '" is deprecated since SimplePie 1.9.'), \E_USER_DEPRECATED); $output = ''; while (($start = strpos($data, '', $start)) !== false) { $data = substr_replace($data, '', 0, $end + 3); } else { $data = ''; } } return $output . $data; } /** * @return int|false */ public static function parse_date(string $dt) { $parser = \SimplePie\Parse\Date::get(); return $parser->parse($dt); } /** * Decode HTML entities * * @deprecated since SimplePie 1.3, use DOMDocument instead * @param string $data Input data * @return string Output data */ public static function entities_decode(string $data) { // trigger_error(sprintf('Using method "' . __METHOD__ . '" is deprecated since SimplePie 1.3, use "DOMDocument" instead.'), \E_USER_DEPRECATED); $decoder = new \SimplePie_Decode_HTML_Entities($data); return $decoder->parse(); } /** * Remove RFC822 comments * * @deprecated since SimplePie 1.9.0. If you need it, consider copying the function to your codebase. * @param string $string Data to strip comments from * @return string Comment stripped string */ public static function uncomment_rfc822(string $string) { // trigger_error(sprintf('Using method "' . __METHOD__ . '" is deprecated since SimplePie 1.9.'), \E_USER_DEPRECATED); $position = 0; $length = strlen($string); $depth = 0; $output = ''; while ($position < $length && ($pos = strpos($string, '(', $position)) !== false) { $output .= substr($string, $position, $pos - $position); $position = $pos + 1; if ($string[$pos - 1] !== '\\') { $depth++; while ($depth && $position < $length) { $position += strcspn($string, '()', $position); if ($string[$position - 1] === '\\') { $position++; continue; } elseif (isset($string[$position])) { switch ($string[$position]) { case '(': $depth++; break; case ')': $depth--; break; } $position++; } else { break; } } } else { $output .= '('; } } $output .= substr($string, $position); return $output; } /** * @return string */ public static function parse_mime(string $mime) { if (($pos = strpos($mime, ';')) === false) { return trim($mime); } return trim(substr($mime, 0, $pos)); } /** * @param array> $attribs * @return int-mask-of */ public static function atom_03_construct_type(array $attribs) { if (isset($attribs['']['mode']) && strtolower(trim($attribs['']['mode'])) === 'base64') { $mode = \SimplePie\SimplePie::CONSTRUCT_BASE64; } else { $mode = \SimplePie\SimplePie::CONSTRUCT_NONE; } if (isset($attribs['']['type'])) { switch (strtolower(trim($attribs['']['type']))) { case 'text': case 'text/plain': return \SimplePie\SimplePie::CONSTRUCT_TEXT | $mode; case 'html': case 'text/html': return \SimplePie\SimplePie::CONSTRUCT_HTML | $mode; case 'xhtml': case 'application/xhtml+xml': return \SimplePie\SimplePie::CONSTRUCT_XHTML | $mode; default: return \SimplePie\SimplePie::CONSTRUCT_NONE | $mode; } } return \SimplePie\SimplePie::CONSTRUCT_TEXT | $mode; } /** * @param array> $attribs * @return int-mask-of */ public static function atom_10_construct_type(array $attribs) { if (isset($attribs['']['type'])) { switch (strtolower(trim($attribs['']['type']))) { case 'text': return \SimplePie\SimplePie::CONSTRUCT_TEXT; case 'html': return \SimplePie\SimplePie::CONSTRUCT_HTML; case 'xhtml': return \SimplePie\SimplePie::CONSTRUCT_XHTML; default: return \SimplePie\SimplePie::CONSTRUCT_NONE; } } return \SimplePie\SimplePie::CONSTRUCT_TEXT; } /** * @param array> $attribs * @return int-mask-of */ public static function atom_10_content_construct_type(array $attribs) { if (isset($attribs['']['type'])) { $type = strtolower(trim($attribs['']['type'])); switch ($type) { case 'text': return \SimplePie\SimplePie::CONSTRUCT_TEXT; case 'html': return \SimplePie\SimplePie::CONSTRUCT_HTML; case 'xhtml': return \SimplePie\SimplePie::CONSTRUCT_XHTML; } if (in_array(substr($type, -4), ['+xml', '/xml']) || substr($type, 0, 5) === 'text/') { return \SimplePie\SimplePie::CONSTRUCT_NONE; } else { return \SimplePie\SimplePie::CONSTRUCT_BASE64; } } return \SimplePie\SimplePie::CONSTRUCT_TEXT; } /** * @return bool */ public static function is_isegment_nz_nc(string $string) { return (bool) preg_match('/^([A-Za-z0-9\-._~\x{A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\x{10000}-\x{1FFFD}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}\x{40000}-\x{4FFFD}\x{50000}-\x{5FFFD}\x{60000}-\x{6FFFD}\x{70000}-\x{7FFFD}\x{80000}-\x{8FFFD}\x{90000}-\x{9FFFD}\x{A0000}-\x{AFFFD}\x{B0000}-\x{BFFFD}\x{C0000}-\x{CFFFD}\x{D0000}-\x{DFFFD}\x{E1000}-\x{EFFFD}!$&\'()*+,;=@]|(%[0-9ABCDEF]{2}))+$/u', $string); } /** * @return string[] */ public static function space_separated_tokens(string $string) { $space_characters = "\x20\x09\x0A\x0B\x0C\x0D"; $string_length = strlen($string); $position = strspn($string, $space_characters); $tokens = []; while ($position < $string_length) { $len = strcspn($string, $space_characters, $position); $tokens[] = substr($string, $position, $len); $position += $len; $position += strspn($string, $space_characters, $position); } return $tokens; } /** * Converts a unicode codepoint to a UTF-8 character * * @static * @param int $codepoint Unicode codepoint * @return string|false UTF-8 character */ public static function codepoint_to_utf8(int $codepoint) { if ($codepoint < 0) { return false; } elseif ($codepoint <= 0x7f) { return chr($codepoint); } elseif ($codepoint <= 0x7ff) { return chr(0xc0 | ($codepoint >> 6)) . chr(0x80 | ($codepoint & 0x3f)); } elseif ($codepoint <= 0xffff) { return chr(0xe0 | ($codepoint >> 12)) . chr(0x80 | (($codepoint >> 6) & 0x3f)) . chr(0x80 | ($codepoint & 0x3f)); } elseif ($codepoint <= 0x10ffff) { return chr(0xf0 | ($codepoint >> 18)) . chr(0x80 | (($codepoint >> 12) & 0x3f)) . chr(0x80 | (($codepoint >> 6) & 0x3f)) . chr(0x80 | ($codepoint & 0x3f)); } // U+FFFD REPLACEMENT CHARACTER return "\xEF\xBF\xBD"; } /** * Similar to parse_str() * * Returns an associative array of name/value pairs, where the value is an * array of values that have used the same name * * @deprecated since SimplePie 1.9.0. If you need it, consider copying the function to your codebase. * @static * @param string $str The input string. * @return array> */ public static function parse_str(string $str) { // trigger_error(sprintf('Using method "' . __METHOD__ . '" is deprecated since SimplePie 1.9.'), \E_USER_DEPRECATED); $return = []; $str = explode('&', $str); foreach ($str as $section) { if (strpos($section, '=') !== false) { [$name, $value] = explode('=', $section, 2); $return[urldecode($name)][] = urldecode($value); } else { $return[urldecode($section)][] = null; } } return $return; } /** * Detect XML encoding, as per XML 1.0 Appendix F.1 * * @todo Add support for EBCDIC * @param string $data XML data * @param \SimplePie\Registry $registry Class registry * @return array Possible encodings */ public static function xml_encoding(string $data, \SimplePie\Registry $registry) { // UTF-32 Big Endian BOM if (substr($data, 0, 4) === "\x00\x00\xFE\xFF") { $encoding[] = 'UTF-32BE'; } // UTF-32 Little Endian BOM elseif (substr($data, 0, 4) === "\xFF\xFE\x00\x00") { $encoding[] = 'UTF-32LE'; } // UTF-16 Big Endian BOM elseif (substr($data, 0, 2) === "\xFE\xFF") { $encoding[] = 'UTF-16BE'; } // UTF-16 Little Endian BOM elseif (substr($data, 0, 2) === "\xFF\xFE") { $encoding[] = 'UTF-16LE'; } // UTF-8 BOM elseif (substr($data, 0, 3) === "\xEF\xBB\xBF") { $encoding[] = 'UTF-8'; } // UTF-32 Big Endian Without BOM elseif (substr($data, 0, 20) === "\x00\x00\x00\x3C\x00\x00\x00\x3F\x00\x00\x00\x78\x00\x00\x00\x6D\x00\x00\x00\x6C") { if ($pos = strpos($data, "\x00\x00\x00\x3F\x00\x00\x00\x3E")) { $parser = $registry->create(Parser::class, [Misc::change_encoding(substr($data, 20, $pos - 20), 'UTF-32BE', 'UTF-8')]); if ($parser->parse()) { $encoding[] = $parser->encoding; } } $encoding[] = 'UTF-32BE'; } // UTF-32 Little Endian Without BOM elseif (substr($data, 0, 20) === "\x3C\x00\x00\x00\x3F\x00\x00\x00\x78\x00\x00\x00\x6D\x00\x00\x00\x6C\x00\x00\x00") { if ($pos = strpos($data, "\x3F\x00\x00\x00\x3E\x00\x00\x00")) { $parser = $registry->create(Parser::class, [Misc::change_encoding(substr($data, 20, $pos - 20), 'UTF-32LE', 'UTF-8')]); if ($parser->parse()) { $encoding[] = $parser->encoding; } } $encoding[] = 'UTF-32LE'; } // UTF-16 Big Endian Without BOM elseif (substr($data, 0, 10) === "\x00\x3C\x00\x3F\x00\x78\x00\x6D\x00\x6C") { if ($pos = strpos($data, "\x00\x3F\x00\x3E")) { $parser = $registry->create(Parser::class, [Misc::change_encoding(substr($data, 20, $pos - 10), 'UTF-16BE', 'UTF-8')]); if ($parser->parse()) { $encoding[] = $parser->encoding; } } $encoding[] = 'UTF-16BE'; } // UTF-16 Little Endian Without BOM elseif (substr($data, 0, 10) === "\x3C\x00\x3F\x00\x78\x00\x6D\x00\x6C\x00") { if ($pos = strpos($data, "\x3F\x00\x3E\x00")) { $parser = $registry->create(Parser::class, [Misc::change_encoding(substr($data, 20, $pos - 10), 'UTF-16LE', 'UTF-8')]); if ($parser->parse()) { $encoding[] = $parser->encoding; } } $encoding[] = 'UTF-16LE'; } // US-ASCII (or superset) elseif (substr($data, 0, 5) === "\x3C\x3F\x78\x6D\x6C") { if ($pos = strpos($data, "\x3F\x3E")) { $parser = $registry->create(Parser::class, [substr($data, 5, $pos - 5)]); if ($parser->parse()) { $encoding[] = $parser->encoding; } } $encoding[] = 'UTF-8'; } // Fallback to UTF-8 else { $encoding[] = 'UTF-8'; } return $encoding; } /** * @return void */ public static function output_javascript() { if (function_exists('ob_gzhandler')) { ob_start('ob_gzhandler'); } header('Content-type: text/javascript; charset: UTF-8'); header('Cache-Control: must-revalidate'); header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 604800) . ' GMT'); // 7 days $body = <<'); } else { document.writeln(''); } } function embed_flash(bgcolor, width, height, link, loop, type) { document.writeln(''); } function embed_flv(width, height, link, placeholder, loop, player) { document.writeln(''); } function embed_wmedia(width, height, link) { document.writeln(''); } JS; echo $body; } /** * Get the SimplePie build timestamp * * Uses the git index if it exists, otherwise uses the modification time * of the newest file. * * @return int */ public static function get_build() { if (self::$SIMPLEPIE_BUILD !== null) { return self::$SIMPLEPIE_BUILD; } $root = dirname(__FILE__, 2); if (file_exists($root . '/.git/index')) { self::$SIMPLEPIE_BUILD = (int) filemtime($root . '/.git/index'); return self::$SIMPLEPIE_BUILD; } elseif (file_exists($root . '/src')) { $time = 0; foreach (glob($root . '/src/*.php') ?: [] as $file) { if (($mtime = filemtime($file)) > $time) { $time = $mtime; } } self::$SIMPLEPIE_BUILD = $time; return self::$SIMPLEPIE_BUILD; } self::$SIMPLEPIE_BUILD = (int) filemtime(__FILE__); return self::$SIMPLEPIE_BUILD; } /** * Get the default user agent string * * @return string */ public static function get_default_useragent() { return \SimplePie\SimplePie::NAME . '/' . \SimplePie\SimplePie::VERSION . ' (Feed Parser; ' . \SimplePie\SimplePie::URL . '; Allow like Gecko) Build/' . static::get_build(); } /** * Format debugging information * * @return string */ public static function debug(SimplePie &$sp) { $info = 'SimplePie ' . \SimplePie\SimplePie::VERSION . ' Build ' . static::get_build() . "\n"; $info .= 'PHP ' . PHP_VERSION . "\n"; if ($sp->error() !== null) { // TODO: Remove cast with multifeeds. $info .= 'Error occurred: ' . implode(', ', (array) $sp->error()) . "\n"; } else { $info .= "No error found.\n"; } $info .= "Extensions:\n"; $extensions = ['pcre', 'curl', 'zlib', 'mbstring', 'iconv', 'xmlreader', 'xml']; foreach ($extensions as $ext) { if (extension_loaded($ext)) { $info .= " $ext loaded\n"; switch ($ext) { case 'pcre': $info .= ' Version ' . PCRE_VERSION . "\n"; break; case 'curl': $version = (array) curl_version(); $info .= ' Version ' . $version['version'] . "\n"; break; case 'iconv': $info .= ' Version ' . ICONV_VERSION . "\n"; break; case 'xml': $info .= ' Version ' . LIBXML_DOTTED_VERSION . "\n"; break; } } else { $info .= " $ext not loaded\n"; } } return $info; } /** * @return bool */ public static function silence_errors(int $num, string $str) { // No-op return true; } /** * Sanitize a URL by removing HTTP credentials. * @param string $url the URL to sanitize. * @return string the same URL without HTTP credentials. */ public static function url_remove_credentials(string $url) { // Cast for PHPStan: I do not think this can fail. // The regex is valid and there should be no backtracking. // https://github.com/phpstan/phpstan/issues/11547 return (string) preg_replace('#^(https?://)[^/:@]+:[^/:@]+@#i', '$1', $url); } } class_alias('SimplePie\Misc', 'SimplePie_Misc', false); RegistryAware.php000064400000000677152505244670010071 0ustar00file = $file; } /** * Get the Content-Type of the specified file * * @return string Actual Content-Type */ public function get_type() { $content_type = $this->file->has_header('content-type') ? $this->file->get_header_line('content-type') : null; $content_encoding = $this->file->has_header('content-encoding') ? $this->file->get_header_line('content-encoding') : null; if ($content_type !== null) { if ($content_encoding === null && ($content_type === 'text/plain' || $content_type === 'text/plain; charset=ISO-8859-1' || $content_type === 'text/plain; charset=iso-8859-1' || $content_type === 'text/plain; charset=UTF-8')) { return $this->text_or_binary(); } if (($pos = strpos($content_type, ';')) !== false) { $official = substr($content_type, 0, $pos); } else { $official = $content_type; } $official = trim(strtolower($official)); if ($official === 'unknown/unknown' || $official === 'application/unknown') { return $this->unknown(); } elseif (substr($official, -4) === '+xml' || $official === 'text/xml' || $official === 'application/xml') { return $official; } elseif (substr($official, 0, 6) === 'image/') { if ($return = $this->image()) { return $return; } return $official; } elseif ($official === 'text/html') { return $this->feed_or_html(); } return $official; } return $this->unknown(); } /** * Sniff text or binary * * @return string Actual Content-Type */ public function text_or_binary() { $body = $this->file->get_body_content(); if (substr($body, 0, 2) === "\xFE\xFF" || substr($body, 0, 2) === "\xFF\xFE" || substr($body, 0, 4) === "\x00\x00\xFE\xFF" || substr($body, 0, 3) === "\xEF\xBB\xBF") { return 'text/plain'; } elseif (preg_match('/[\x00-\x08\x0E-\x1A\x1C-\x1F]/', $body)) { return 'application/octet-stream'; } return 'text/plain'; } /** * Sniff unknown * * @return string Actual Content-Type */ public function unknown() { $body = $this->file->get_body_content(); $ws = strspn($body, "\x09\x0A\x0B\x0C\x0D\x20"); if (strtolower(substr($body, $ws, 14)) === 'text_or_binary(); } /** * Sniff images * * @return string|false Actual Content-Type */ public function image() { $body = $this->file->get_body_content(); if (substr($body, 0, 6) === 'GIF87a' || substr($body, 0, 6) === 'GIF89a') { return 'image/gif'; } elseif (substr($body, 0, 8) === "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A") { return 'image/png'; } elseif (substr($body, 0, 3) === "\xFF\xD8\xFF") { return 'image/jpeg'; } elseif (substr($body, 0, 2) === "\x42\x4D") { return 'image/bmp'; } elseif (substr($body, 0, 4) === "\x00\x00\x01\x00") { return 'image/vnd.microsoft.icon'; } return false; } /** * Sniff HTML * * @return string Actual Content-Type */ public function feed_or_html() { $body = $this->file->get_body_content(); $len = strlen($body); $pos = strspn($body, "\x09\x0A\x0D\x20\xEF\xBB\xBF"); while ($pos < $len) { switch ($body[$pos]) { case "\x09": case "\x0A": case "\x0D": case "\x20": $pos += strspn($body, "\x09\x0A\x0D\x20", $pos); continue 2; case '<': $pos++; break; default: return 'text/html'; } if (substr($body, $pos, 3) === '!--') { $pos += 3; if ($pos < $len && ($pos = strpos($body, '-->', $pos)) !== false) { $pos += 3; } else { return 'text/html'; } } elseif (substr($body, $pos, 1) === '!') { if ($pos < $len && ($pos = strpos($body, '>', $pos)) !== false) { $pos++; } else { return 'text/html'; } } elseif (substr($body, $pos, 1) === '?') { if ($pos < $len && ($pos = strpos($body, '?>', $pos)) !== false) { $pos += 2; } else { return 'text/html'; } } elseif (substr($body, $pos, 3) === 'rss' || substr($body, $pos, 7) === 'rdf:RDF') { return 'application/rss+xml'; } elseif (substr($body, $pos, 4) === 'feed') { return 'application/atom+xml'; } else { return 'text/html'; } } return 'text/html'; } } class_alias('SimplePie\Content\Type\Sniffer', 'SimplePie_Content_Type_Sniffer'); Item.php000064400000401045152505244670006171 0ustar00 */ public $data = []; /** * Registry object * * @see set_registry * @var \SimplePie\Registry */ protected $registry; /** * @var Sanitize|null */ private $sanitize = null; /** * Create a new item object * * This is usually used by {@see \SimplePie\SimplePie::get_items} and * {@see \SimplePie\SimplePie::get_item}. Avoid creating this manually. * * @param \SimplePie\SimplePie $feed Parent feed * @param array $data Raw data */ public function __construct(\SimplePie\SimplePie $feed, array $data) { $this->feed = $feed; $this->data = $data; } /** * Set the registry handler * * This is usually used by {@see \SimplePie\Registry::create} * * @since 1.3 * @param \SimplePie\Registry $registry * @return void */ public function set_registry(\SimplePie\Registry $registry) { $this->registry = $registry; } /** * Get a string representation of the item * * @return string */ public function __toString() { return md5(serialize($this->data)); } /** * Remove items that link back to this before destroying this object */ public function __destruct() { if (!gc_enabled()) { unset($this->feed); } } /** * Get data for an item-level element * * This method allows you to get access to ANY element/attribute that is a * sub-element of the item/entry tag. * * See {@see \SimplePie\SimplePie::get_feed_tags()} for a description of the return value * * @since 1.0 * @see http://simplepie.org/wiki/faq/supported_xml_namespaces * @param string $namespace The URL of the XML namespace of the elements you're trying to access * @param string $tag Tag name * @return array>|null */ public function get_item_tags(string $namespace, string $tag) { if (isset($this->data['child'][$namespace][$tag])) { return $this->data['child'][$namespace][$tag]; } return null; } /** * Get base URL of the item itself. * Returns `` or feed base URL. * Similar to `Item::get_base()` but can safely be used during initialisation methods * such as `Item::get_links()` (`Item::get_base()` and `Item::get_links()` call each-other) * and is not affected by enclosures. * * @param array $element * @see get_base */ private function get_own_base(array $element = []): string { if (!empty($element['xml_base_explicit']) && isset($element['xml_base'])) { return $element['xml_base']; } return $this->feed->get_base(); } /** * Get the base URL value. * Uses ``, or item link, or enclosure link, or feed base URL. * * @param array $element * @return string */ public function get_base(array $element = []) { if (!empty($element['xml_base_explicit']) && isset($element['xml_base'])) { return $element['xml_base']; } $link = $this->get_permalink(); if ($link != null) { return $link; } return $this->feed->get_base($element); } /** * Sanitize feed data * * @access private * @see \SimplePie\SimplePie::sanitize() * @param string $data Data to sanitize * @param int-mask-of $type * @param string $base Base URL to resolve URLs against * @return string Sanitized data */ public function sanitize(string $data, int $type, string $base = '') { // This really returns string|false but changing encoding is uncommon and we are going to deprecate it, so let’s just lie to PHPStan in the interest of cleaner annotations. return $this->feed->sanitize($data, $type, $base); } /** * Get the parent feed * * Note: this may not work as you think for multifeeds! * * @link http://simplepie.org/faq/typical_multifeed_gotchas#missing_data_from_feed * @since 1.0 * @return \SimplePie\SimplePie */ public function get_feed() { return $this->feed; } /** * Get the unique identifier for the item * * This is usually used when writing code to check for new items in a feed. * * Uses ``, ``, `` or the `about` attribute * for RDF. If none of these are supplied (or `$hash` is true), creates an * MD5 hash based on the permalink, title and content. * * @since Beta 2 * @param bool $hash Should we force using a hash instead of the supplied ID? * @param string|false $fn User-supplied function to generate an hash * @return string|null */ public function get_id(bool $hash = false, $fn = 'md5') { if (!$hash) { if ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'id')) { return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_03, 'id')) { return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_20, 'guid')) { return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_DC_11, 'identifier')) { return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_DC_10, 'identifier')) { return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } elseif (isset($this->data['attribs'][\SimplePie\SimplePie::NAMESPACE_RDF]['about'])) { return $this->sanitize($this->data['attribs'][\SimplePie\SimplePie::NAMESPACE_RDF]['about'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } } if ($fn === false) { return null; } elseif (!is_callable($fn)) { trigger_error('User-supplied function $fn must be callable', E_USER_WARNING); $fn = 'md5'; } return call_user_func( $fn, $this->get_permalink().$this->get_title().$this->get_content() ); } /** * Get the title of the item * * Uses ``, `` or `<dc:title>` * * @since Beta 2 (previously called `get_item_title` since 0.8) * @return string|null */ public function get_title() { if (!isset($this->data['title'])) { if ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'title')) { $this->data['title'] = $this->sanitize($return[0]['data'], $this->registry->call(Misc::class, 'atom_10_construct_type', [$return[0]['attribs']]), $this->get_base($return[0])); } elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_03, 'title')) { $this->data['title'] = $this->sanitize($return[0]['data'], $this->registry->call(Misc::class, 'atom_03_construct_type', [$return[0]['attribs']]), $this->get_base($return[0])); } elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_10, 'title')) { $this->data['title'] = $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); } elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_090, 'title')) { $this->data['title'] = $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); } elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_20, 'title')) { $this->data['title'] = $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); } elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_DC_11, 'title')) { $this->data['title'] = $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_DC_10, 'title')) { $this->data['title'] = $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } else { $this->data['title'] = null; } } return $this->data['title']; } /** * Get the content for the item * * Prefers summaries over full content , but will return full content if a * summary does not exist. * * To prefer full content instead, use {@see get_content} * * Uses `<atom:summary>`, `<description>`, `<dc:description>` or * `<itunes:subtitle>` * * @since 0.8 * @param bool $description_only Should we avoid falling back to the content? * @return string|null */ public function get_description(bool $description_only = false) { if (($tags = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'summary')) && ($return = $this->sanitize($tags[0]['data'], $this->registry->call(Misc::class, 'atom_10_construct_type', [$tags[0]['attribs']]), $this->get_base($tags[0])))) { return $return; } elseif (($tags = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_03, 'summary')) && ($return = $this->sanitize($tags[0]['data'], $this->registry->call(Misc::class, 'atom_03_construct_type', [$tags[0]['attribs']]), $this->get_base($tags[0])))) { return $return; } elseif (($tags = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_10, 'description')) && ($return = $this->sanitize($tags[0]['data'], \SimplePie\SimplePie::CONSTRUCT_MAYBE_HTML, $this->get_base($tags[0])))) { return $return; } elseif (($tags = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_20, 'description')) && ($return = $this->sanitize($tags[0]['data'], \SimplePie\SimplePie::CONSTRUCT_HTML, $this->get_base($tags[0])))) { return $return; } elseif (($tags = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_DC_11, 'description')) && ($return = $this->sanitize($tags[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT))) { return $return; } elseif (($tags = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_DC_10, 'description')) && ($return = $this->sanitize($tags[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT))) { return $return; } elseif (($tags = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ITUNES, 'summary')) && ($return = $this->sanitize($tags[0]['data'], \SimplePie\SimplePie::CONSTRUCT_HTML, $this->get_base($tags[0])))) { return $return; } elseif (($tags = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ITUNES, 'subtitle')) && ($return = $this->sanitize($tags[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT))) { return $return; } elseif (($tags = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_090, 'description')) && ($return = $this->sanitize($tags[0]['data'], \SimplePie\SimplePie::CONSTRUCT_HTML))) { return $return; } elseif (!$description_only) { return $this->get_content(true); } return null; } /** * Get the content for the item * * Prefers full content over summaries, but will return a summary if full * content does not exist. * * To prefer summaries instead, use {@see get_description} * * Uses `<atom:content>` or `<content:encoded>` (RSS 1.0 Content Module) * * @since 1.0 * @param bool $content_only Should we avoid falling back to the description? * @return string|null */ public function get_content(bool $content_only = false) { if (($tags = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'content')) && ($return = $this->sanitize($tags[0]['data'], $this->registry->call(Misc::class, 'atom_10_content_construct_type', [$tags[0]['attribs']]), $this->get_base($tags[0])))) { return $return; } elseif (($tags = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_03, 'content')) && ($return = $this->sanitize($tags[0]['data'], $this->registry->call(Misc::class, 'atom_03_construct_type', [$tags[0]['attribs']]), $this->get_base($tags[0])))) { return $return; } elseif (($tags = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_10_MODULES_CONTENT, 'encoded')) && ($return = $this->sanitize($tags[0]['data'], \SimplePie\SimplePie::CONSTRUCT_HTML, $this->get_base($tags[0])))) { return $return; } elseif (!$content_only) { return $this->get_description(true); } return null; } /** * Get the media:thumbnail of the item * * Uses `<media:thumbnail>` * * * @return array{url: string, height?: string, width?: string, time?: string}|null */ public function get_thumbnail() { if (!isset($this->data['thumbnail'])) { if ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'thumbnail')) { $thumbnail = $return[0]['attribs']['']; if (empty($thumbnail['url'])) { $this->data['thumbnail'] = null; } else { $thumbnail['url'] = $this->sanitize($thumbnail['url'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_base($return[0])); $this->data['thumbnail'] = $thumbnail; } } else { $this->data['thumbnail'] = null; } } return $this->data['thumbnail']; } /** * Get a category for the item * * @since Beta 3 (previously called `get_categories()` since Beta 2) * @param int $key The category that you want to return. Remember that arrays begin with 0, not 1 * @return \SimplePie\Category|null */ public function get_category(int $key = 0) { $categories = $this->get_categories(); if (isset($categories[$key])) { return $categories[$key]; } return null; } /** * Get all categories for the item * * Uses `<atom:category>`, `<category>` or `<dc:subject>` * * @since Beta 3 * @return \SimplePie\Category[]|null List of {@see \SimplePie\Category} objects */ public function get_categories() { $categories = []; $type = 'category'; foreach ((array) $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, $type) as $category) { $term = null; $scheme = null; $label = null; if (isset($category['attribs']['']['term'])) { $term = $this->sanitize($category['attribs']['']['term'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($category['attribs']['']['scheme'])) { $scheme = $this->sanitize($category['attribs']['']['scheme'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($category['attribs']['']['label'])) { $label = $this->sanitize($category['attribs']['']['label'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } $categories[] = $this->registry->create(Category::class, [$term, $scheme, $label, $type]); } foreach ((array) $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_20, $type) as $category) { // This is really the label, but keep this as the term also for BC. // Label will also work on retrieving because that falls back to term. $term = $this->sanitize($category['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); if (isset($category['attribs']['']['domain'])) { $scheme = $this->sanitize($category['attribs']['']['domain'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } else { $scheme = null; } $categories[] = $this->registry->create(Category::class, [$term, $scheme, null, $type]); } $type = 'subject'; foreach ((array) $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_DC_11, $type) as $category) { $categories[] = $this->registry->create(Category::class, [$this->sanitize($category['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT), null, null, $type]); } foreach ((array) $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_DC_10, $type) as $category) { $categories[] = $this->registry->create(Category::class, [$this->sanitize($category['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT), null, null, $type]); } if (!empty($categories)) { return array_unique($categories); } return null; } /** * Get an author for the item * * @since Beta 2 * @param int $key The author that you want to return. Remember that arrays begin with 0, not 1 * @return \SimplePie\Author|null */ public function get_author(int $key = 0) { $authors = $this->get_authors(); if (isset($authors[$key])) { return $authors[$key]; } return null; } /** * Get a contributor for the item * * @since 1.1 * @param int $key The contrbutor that you want to return. Remember that arrays begin with 0, not 1 * @return \SimplePie\Author|null */ public function get_contributor(int $key = 0) { $contributors = $this->get_contributors(); if (isset($contributors[$key])) { return $contributors[$key]; } return null; } /** * Get all contributors for the item * * Uses `<atom:contributor>` * * @since 1.1 * @return \SimplePie\Author[]|null List of {@see \SimplePie\Author} objects */ public function get_contributors() { $contributors = []; foreach ((array) $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'contributor') as $contributor) { $name = null; $uri = null; $email = null; if (isset($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['name'][0]['data'])) { $name = $this->sanitize($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['name'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['uri'][0]['data'])) { $uri = $contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['uri'][0]; $uri = $this->sanitize($uri['data'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_base($uri)); } if (isset($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['email'][0]['data'])) { $email = $this->sanitize($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['email'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if ($name !== null || $email !== null || $uri !== null) { $contributors[] = $this->registry->create(Author::class, [$name, $uri, $email]); } } foreach ((array) $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_03, 'contributor') as $contributor) { $name = null; $url = null; $email = null; if (isset($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['name'][0]['data'])) { $name = $this->sanitize($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['name'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['url'][0]['data'])) { $url = $contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['url'][0]; $url = $this->sanitize($url['data'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_base($url)); } if (isset($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['email'][0]['data'])) { $email = $this->sanitize($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['email'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if ($name !== null || $email !== null || $url !== null) { $contributors[] = $this->registry->create(Author::class, [$name, $url, $email]); } } if (!empty($contributors)) { return array_unique($contributors); } return null; } /** * Get all authors for the item * * Uses `<atom:author>`, `<author>`, `<dc:creator>` or `<itunes:author>` * * @since Beta 2 * @return \SimplePie\Author[]|null List of {@see \SimplePie\Author} objects */ public function get_authors() { $authors = []; foreach ((array) $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'author') as $author) { $name = null; $uri = null; $email = null; if (isset($author['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['name'][0]['data'])) { $name = $this->sanitize($author['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['name'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($author['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['uri'][0]['data'])) { $uri = $author['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['uri'][0]; $uri = $this->sanitize($uri['data'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_base($uri)); } if (isset($author['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['email'][0]['data'])) { $email = $this->sanitize($author['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['email'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if ($name !== null || $email !== null || $uri !== null) { $authors[] = $this->registry->create(Author::class, [$name, $uri, $email]); } } if ($author = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_03, 'author')) { $name = null; $url = null; $email = null; if (isset($author[0]['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['name'][0]['data'])) { $name = $this->sanitize($author[0]['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['name'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($author[0]['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['url'][0]['data'])) { $url = $author[0]['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['url'][0]; $url = $this->sanitize($url['data'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_base($url)); } if (isset($author[0]['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['email'][0]['data'])) { $email = $this->sanitize($author[0]['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['email'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if ($name !== null || $email !== null || $url !== null) { $authors[] = $this->registry->create(Author::class, [$name, $url, $email]); } } if ($author = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_20, 'author')) { $authors[] = $this->registry->create(Author::class, [null, null, $this->sanitize($author[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT)]); } foreach ((array) $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_DC_11, 'creator') as $author) { $authors[] = $this->registry->create(Author::class, [$this->sanitize($author['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT), null, null]); } foreach ((array) $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_DC_10, 'creator') as $author) { $authors[] = $this->registry->create(Author::class, [$this->sanitize($author['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT), null, null]); } foreach ((array) $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ITUNES, 'author') as $author) { $authors[] = $this->registry->create(Author::class, [$this->sanitize($author['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT), null, null]); } if (!empty($authors)) { return array_unique($authors); } elseif (($source = $this->get_source()) && ($authors = $source->get_authors())) { return $authors; } elseif ($authors = $this->feed->get_authors()) { return $authors; } return null; } /** * Get the copyright info for the item * * Uses `<atom:rights>` or `<dc:rights>` * * @since 1.1 * @return string|null */ public function get_copyright() { if ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'rights')) { return $this->sanitize($return[0]['data'], $this->registry->call(Misc::class, 'atom_10_construct_type', [$return[0]['attribs']]), $this->get_base($return[0])); } elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_DC_11, 'rights')) { return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_DC_10, 'rights')) { return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } return null; } /** * Get the posting date/time for the item * * Uses `<atom:published>`, `<atom:updated>`, `<atom:issued>`, * `<atom:modified>`, `<pubDate>` or `<dc:date>` * * Note: obeys PHP's timezone setting. To get a UTC date/time, use * {@see get_gmdate} * * @since Beta 2 (previously called `get_item_date` since 0.8) * * @param string $date_format Supports any PHP date format from {@see http://php.net/date} (empty for the raw data) * @return ($date_format is 'U' ? ?int : ?string) */ public function get_date(string $date_format = 'j F Y, g:i a') { if (!isset($this->data['date'])) { if ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'published')) { $this->data['date']['raw'] = $return[0]['data']; } elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_20, 'pubDate')) { $this->data['date']['raw'] = $return[0]['data']; } elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_DC_11, 'date')) { $this->data['date']['raw'] = $return[0]['data']; } elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_DC_10, 'date')) { $this->data['date']['raw'] = $return[0]['data']; } elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'updated')) { $this->data['date']['raw'] = $return[0]['data']; } elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_03, 'issued')) { $this->data['date']['raw'] = $return[0]['data']; } elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_03, 'created')) { $this->data['date']['raw'] = $return[0]['data']; } elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_03, 'modified')) { $this->data['date']['raw'] = $return[0]['data']; } if (!empty($this->data['date']['raw'])) { $parser = $this->registry->call(Parse\Date::class, 'get'); $this->data['date']['parsed'] = $parser->parse($this->data['date']['raw']) ?: null; } else { $this->data['date'] = null; } } if ($this->data['date']) { switch ($date_format) { case '': return $this->sanitize($this->data['date']['raw'], \SimplePie\SimplePie::CONSTRUCT_TEXT); case 'U': return $this->data['date']['parsed']; default: return date($date_format, $this->data['date']['parsed']); } } return null; } /** * Get the update date/time for the item * * Uses `<atom:updated>` * * Note: obeys PHP's timezone setting. To get a UTC date/time, use * {@see get_gmdate} * * @param string $date_format Supports any PHP date format from {@see http://php.net/date} (empty for the raw data) * @return ($date_format is 'U' ? ?int : ?string) */ public function get_updated_date(string $date_format = 'j F Y, g:i a') { if (!isset($this->data['updated'])) { if ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'updated')) { $this->data['updated']['raw'] = $return[0]['data']; } if (!empty($this->data['updated']['raw'])) { $parser = $this->registry->call(Parse\Date::class, 'get'); $this->data['updated']['parsed'] = $parser->parse($this->data['updated']['raw']) ?: null; } else { $this->data['updated'] = null; } } if ($this->data['updated']) { switch ($date_format) { case '': return $this->sanitize($this->data['updated']['raw'], \SimplePie\SimplePie::CONSTRUCT_TEXT); case 'U': return $this->data['updated']['parsed']; default: return date($date_format, $this->data['updated']['parsed']); } } return null; } /** * Get the localized posting date/time for the item * * Returns the date formatted in the localized language. To display in * languages other than the server's default, you need to change the locale * with {@link http://php.net/setlocale setlocale()}. The available * localizations depend on which ones are installed on your web server. * * @since 1.0 * * @param string $date_format Supports any PHP date format from {@see http://php.net/strftime} (empty for the raw data) * @return string|null|false see `strftime` for when this can return `false` */ public function get_local_date(string $date_format = '%c') { if ($date_format === '') { if (($raw_date = $this->get_date('')) === null) { return null; } return $this->sanitize($raw_date, \SimplePie\SimplePie::CONSTRUCT_TEXT); } elseif (($date = $this->get_date('U')) !== null && $date !== false) { return strftime($date_format, $date); } return null; } /** * Get the posting date/time for the item (UTC time) * * @see get_date * @param string $date_format Supports any PHP date format from {@see http://php.net/date} * @return string|null */ public function get_gmdate(string $date_format = 'j F Y, g:i a') { $date = $this->get_date('U'); if ($date === null) { return null; } return gmdate($date_format, $date); } /** * Get the update date/time for the item (UTC time) * * @see get_updated_date * @param string $date_format Supports any PHP date format from {@see http://php.net/date} * @return string|null */ public function get_updated_gmdate(string $date_format = 'j F Y, g:i a') { $date = $this->get_updated_date('U'); if ($date === null) { return null; } return gmdate($date_format, $date); } /** * Get the permalink for the item * * Returns the first link available with a relationship of "alternate". * Identical to {@see get_link()} with key 0 * * @see get_link * @since 0.8 * @return string|null Permalink URL */ public function get_permalink() { $link = $this->get_link(); $enclosure = $this->get_enclosure(0); if ($link !== null) { return $link; } elseif ($enclosure !== null) { return $enclosure->get_link(); } return null; } /** * Get a single link for the item * * @since Beta 3 * @param int $key The link that you want to return. Remember that arrays begin with 0, not 1 * @param string $rel The relationship of the link to return * @return string|null Link URL */ public function get_link(int $key = 0, string $rel = 'alternate') { $links = $this->get_links($rel); if ($links && $links[$key] !== null) { return $links[$key]; } return null; } /** * Get all links for the item * * Uses `<atom:link>`, `<link>` or `<guid>` * * @since Beta 2 * @param string $rel The relationship of links to return * @return array<string>|null Links found for the item (strings) */ public function get_links(string $rel = 'alternate') { if (!isset($this->data['links'])) { $this->data['links'] = []; foreach ((array) $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'link') as $link) { if (isset($link['attribs']['']['href'])) { $link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate'; $this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($link)); } } foreach ((array) $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_03, 'link') as $link) { if (isset($link['attribs']['']['href'])) { $link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate'; $this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($link)); } } if ($links = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_10, 'link')) { $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($links[0])); } if ($links = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_090, 'link')) { $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($links[0])); } if ($links = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_20, 'link')) { $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($links[0])); } if ($links = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_20, 'guid')) { if (!isset($links[0]['attribs']['']['isPermaLink']) || strtolower(trim($links[0]['attribs']['']['isPermaLink'])) === 'true') { $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($links[0])); } } $keys = array_keys($this->data['links']); foreach ($keys as $key) { if ($this->registry->call(Misc::class, 'is_isegment_nz_nc', [$key])) { if (isset($this->data['links'][\SimplePie\SimplePie::IANA_LINK_RELATIONS_REGISTRY . $key])) { $this->data['links'][\SimplePie\SimplePie::IANA_LINK_RELATIONS_REGISTRY . $key] = array_merge($this->data['links'][$key], $this->data['links'][\SimplePie\SimplePie::IANA_LINK_RELATIONS_REGISTRY . $key]); $this->data['links'][$key] = &$this->data['links'][\SimplePie\SimplePie::IANA_LINK_RELATIONS_REGISTRY . $key]; } else { $this->data['links'][\SimplePie\SimplePie::IANA_LINK_RELATIONS_REGISTRY . $key] = &$this->data['links'][$key]; } } elseif (substr((string) $key, 0, 41) === \SimplePie\SimplePie::IANA_LINK_RELATIONS_REGISTRY) { $this->data['links'][substr((string) $key, 41)] = &$this->data['links'][$key]; } $this->data['links'][$key] = array_unique($this->data['links'][$key]); } } if (isset($this->data['links'][$rel])) { return $this->data['links'][$rel]; } return null; } /** * Get an enclosure from the item * * Supports the <enclosure> RSS tag, as well as Media RSS and iTunes RSS. * * @since Beta 2 * @todo Add ability to prefer one type of content over another (in a media group). * @param int $key The enclosure that you want to return. Remember that arrays begin with 0, not 1 * @return \SimplePie\Enclosure|null */ public function get_enclosure(int $key = 0) { $enclosures = $this->get_enclosures(); if (isset($enclosures[$key])) { return $enclosures[$key]; } return null; } /** * Get all available enclosures (podcasts, etc.) * * Supports the <enclosure> RSS tag, as well as Media RSS and iTunes RSS. * * At this point, we're pretty much assuming that all enclosures for an item * are the same content. Anything else is too complicated to * properly support. * * @since Beta 2 * @todo Add support for end-user defined sorting of enclosures by type/handler (so we can prefer the faster-loading FLV over MP4). * @todo If an element exists at a level, but its value is empty, we should fall back to the value from the parent (if it exists). * @return \SimplePie\Enclosure[]|null List of \SimplePie\Enclosure items */ public function get_enclosures() { if (!isset($this->data['enclosures'])) { $this->data['enclosures'] = []; // Elements $captions_parent = null; $categories_parent = null; $copyrights_parent = null; $credits_parent = null; $description_parent = null; $duration_parent = null; $hashes_parent = null; $keywords_parent = null; $player_parent = null; $ratings_parent = null; $restrictions_parent = []; $thumbnails_parent = null; $title_parent = null; // Let's do the channel and item-level ones first, and just re-use them if we need to. $parent = $this->get_feed(); // CAPTIONS if ($captions = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'text')) { foreach ($captions as $caption) { $caption_type = null; $caption_lang = null; $caption_startTime = null; $caption_endTime = null; $caption_text = null; if (isset($caption['attribs']['']['type'])) { $caption_type = $this->sanitize($caption['attribs']['']['type'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($caption['attribs']['']['lang'])) { $caption_lang = $this->sanitize($caption['attribs']['']['lang'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($caption['attribs']['']['start'])) { $caption_startTime = $this->sanitize($caption['attribs']['']['start'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($caption['attribs']['']['end'])) { $caption_endTime = $this->sanitize($caption['attribs']['']['end'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($caption['data'])) { $caption_text = $this->sanitize($caption['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } $captions_parent[] = $this->registry->create(Caption::class, [$caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text]); } } elseif ($captions = $parent->get_channel_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'text')) { foreach ($captions as $caption) { $caption_type = null; $caption_lang = null; $caption_startTime = null; $caption_endTime = null; $caption_text = null; if (isset($caption['attribs']['']['type'])) { $caption_type = $this->sanitize($caption['attribs']['']['type'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($caption['attribs']['']['lang'])) { $caption_lang = $this->sanitize($caption['attribs']['']['lang'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($caption['attribs']['']['start'])) { $caption_startTime = $this->sanitize($caption['attribs']['']['start'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($caption['attribs']['']['end'])) { $caption_endTime = $this->sanitize($caption['attribs']['']['end'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($caption['data'])) { $caption_text = $this->sanitize($caption['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } $captions_parent[] = $this->registry->create(Caption::class, [$caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text]); } } if (is_array($captions_parent)) { $captions_parent = array_values(array_unique($captions_parent)); } // CATEGORIES foreach ((array) $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'category') as $category) { $term = null; $scheme = null; $label = null; if (isset($category['data'])) { $term = $this->sanitize($category['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($category['attribs']['']['scheme'])) { $scheme = $this->sanitize($category['attribs']['']['scheme'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } else { $scheme = 'http://search.yahoo.com/mrss/category_schema'; } if (isset($category['attribs']['']['label'])) { $label = $this->sanitize($category['attribs']['']['label'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } $categories_parent[] = $this->registry->create(Category::class, [$term, $scheme, $label]); } foreach ((array) $parent->get_channel_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'category') as $category) { $term = null; $scheme = null; $label = null; if (isset($category['data'])) { $term = $this->sanitize($category['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($category['attribs']['']['scheme'])) { $scheme = $this->sanitize($category['attribs']['']['scheme'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } else { $scheme = 'http://search.yahoo.com/mrss/category_schema'; } if (isset($category['attribs']['']['label'])) { $label = $this->sanitize($category['attribs']['']['label'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } $categories_parent[] = $this->registry->create(Category::class, [$term, $scheme, $label]); } foreach ((array) $parent->get_channel_tags(\SimplePie\SimplePie::NAMESPACE_ITUNES, 'category') as $category) { $term = null; $scheme = 'http://www.itunes.com/dtds/podcast-1.0.dtd'; $label = null; if (isset($category['attribs']['']['text'])) { $label = $this->sanitize($category['attribs']['']['text'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } $categories_parent[] = $this->registry->create(Category::class, [$term, $scheme, $label]); if (isset($category['child'][\SimplePie\SimplePie::NAMESPACE_ITUNES]['category'])) { foreach ((array) $category['child'][\SimplePie\SimplePie::NAMESPACE_ITUNES]['category'] as $subcategory) { if (isset($subcategory['attribs']['']['text'])) { $label = $this->sanitize($subcategory['attribs']['']['text'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } $categories_parent[] = $this->registry->create(Category::class, [$term, $scheme, $label]); } } } if (is_array($categories_parent)) { $categories_parent = array_values(array_unique($categories_parent)); } // COPYRIGHT if ($copyright = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'copyright')) { $copyright_url = null; $copyright_label = null; if (isset($copyright[0]['attribs']['']['url'])) { $copyright_url = $this->sanitize($copyright[0]['attribs']['']['url'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($copyright[0]['data'])) { $copyright_label = $this->sanitize($copyright[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } $copyrights_parent = $this->registry->create(Copyright::class, [$copyright_url, $copyright_label]); } elseif ($copyright = $parent->get_channel_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'copyright')) { $copyright_url = null; $copyright_label = null; if (isset($copyright[0]['attribs']['']['url'])) { $copyright_url = $this->sanitize($copyright[0]['attribs']['']['url'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($copyright[0]['data'])) { $copyright_label = $this->sanitize($copyright[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } $copyrights_parent = $this->registry->create(Copyright::class, [$copyright_url, $copyright_label]); } // CREDITS if ($credits = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'credit')) { foreach ($credits as $credit) { $credit_role = null; $credit_scheme = null; $credit_name = null; if (isset($credit['attribs']['']['role'])) { $credit_role = $this->sanitize($credit['attribs']['']['role'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($credit['attribs']['']['scheme'])) { $credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } else { $credit_scheme = 'urn:ebu'; } if (isset($credit['data'])) { $credit_name = $this->sanitize($credit['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } $credits_parent[] = $this->registry->create(Credit::class, [$credit_role, $credit_scheme, $credit_name]); } } elseif ($credits = $parent->get_channel_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'credit')) { foreach ($credits as $credit) { $credit_role = null; $credit_scheme = null; $credit_name = null; if (isset($credit['attribs']['']['role'])) { $credit_role = $this->sanitize($credit['attribs']['']['role'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($credit['attribs']['']['scheme'])) { $credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } else { $credit_scheme = 'urn:ebu'; } if (isset($credit['data'])) { $credit_name = $this->sanitize($credit['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } $credits_parent[] = $this->registry->create(Credit::class, [$credit_role, $credit_scheme, $credit_name]); } } if (is_array($credits_parent)) { $credits_parent = array_values(array_unique($credits_parent)); } // DESCRIPTION if ($description_parent = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'description')) { if (isset($description_parent[0]['data'])) { $description_parent = $this->sanitize($description_parent[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } } elseif ($description_parent = $parent->get_channel_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'description')) { if (isset($description_parent[0]['data'])) { $description_parent = $this->sanitize($description_parent[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } } // DURATION $duration_tags = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ITUNES, 'duration'); if ($duration_tags !== null) { $seconds = null; $minutes = null; $hours = null; if (isset($duration_tags[0]['data'])) { $temp = explode(':', $this->sanitize($duration_tags[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT)); $seconds = (int) array_pop($temp); if (count($temp) > 0) { $minutes = (int) array_pop($temp); $seconds += $minutes * 60; } if (count($temp) > 0) { $hours = (int) array_pop($temp); $seconds += $hours * 3600; } unset($temp); $duration_parent = $seconds; } } // HASHES if ($hashes_iterator = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'hash')) { foreach ($hashes_iterator as $hash) { $value = null; $algo = null; if (isset($hash['data'])) { $value = $this->sanitize($hash['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($hash['attribs']['']['algo'])) { $algo = $this->sanitize($hash['attribs']['']['algo'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } else { $algo = 'md5'; } $hashes_parent[] = $algo.':'.$value; } } elseif ($hashes_iterator = $parent->get_channel_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'hash')) { foreach ($hashes_iterator as $hash) { $value = null; $algo = null; if (isset($hash['data'])) { $value = $this->sanitize($hash['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($hash['attribs']['']['algo'])) { $algo = $this->sanitize($hash['attribs']['']['algo'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } else { $algo = 'md5'; } $hashes_parent[] = $algo.':'.$value; } } if (is_array($hashes_parent)) { $hashes_parent = array_values(array_unique($hashes_parent)); } // KEYWORDS if ($keywords = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'keywords')) { if (isset($keywords[0]['data'])) { $temp = explode(',', $this->sanitize($keywords[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT)); foreach ($temp as $word) { $keywords_parent[] = trim($word); } } unset($temp); } elseif ($keywords = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ITUNES, 'keywords')) { if (isset($keywords[0]['data'])) { $temp = explode(',', $this->sanitize($keywords[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT)); foreach ($temp as $word) { $keywords_parent[] = trim($word); } } unset($temp); } elseif ($keywords = $parent->get_channel_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'keywords')) { if (isset($keywords[0]['data'])) { $temp = explode(',', $this->sanitize($keywords[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT)); foreach ($temp as $word) { $keywords_parent[] = trim($word); } } unset($temp); } elseif ($keywords = $parent->get_channel_tags(\SimplePie\SimplePie::NAMESPACE_ITUNES, 'keywords')) { if (isset($keywords[0]['data'])) { $temp = explode(',', $this->sanitize($keywords[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT)); foreach ($temp as $word) { $keywords_parent[] = trim($word); } } unset($temp); } if (is_array($keywords_parent)) { $keywords_parent = array_values(array_unique($keywords_parent)); } // PLAYER if ($player_parent = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'player')) { if (isset($player_parent[0]['attribs']['']['url'])) { $player_parent = $this->sanitize($player_parent[0]['attribs']['']['url'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($player_parent[0])); } } elseif ($player_parent = $parent->get_channel_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'player')) { if (isset($player_parent[0]['attribs']['']['url'])) { $player_parent = $this->sanitize($player_parent[0]['attribs']['']['url'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($player_parent[0])); } } // RATINGS if ($ratings = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'rating')) { foreach ($ratings as $rating) { $rating_scheme = null; $rating_value = null; if (isset($rating['attribs']['']['scheme'])) { $rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } else { $rating_scheme = 'urn:simple'; } if (isset($rating['data'])) { $rating_value = $this->sanitize($rating['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } $ratings_parent[] = $this->registry->create(Rating::class, [$rating_scheme, $rating_value]); } } elseif ($ratings = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ITUNES, 'explicit')) { foreach ($ratings as $rating) { $rating_scheme = 'urn:itunes'; $rating_value = null; if (isset($rating['data'])) { $rating_value = $this->sanitize($rating['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } $ratings_parent[] = $this->registry->create(Rating::class, [$rating_scheme, $rating_value]); } } elseif ($ratings = $parent->get_channel_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'rating')) { foreach ($ratings as $rating) { $rating_scheme = null; $rating_value = null; if (isset($rating['attribs']['']['scheme'])) { $rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } else { $rating_scheme = 'urn:simple'; } if (isset($rating['data'])) { $rating_value = $this->sanitize($rating['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } $ratings_parent[] = $this->registry->create(Rating::class, [$rating_scheme, $rating_value]); } } elseif ($ratings = $parent->get_channel_tags(\SimplePie\SimplePie::NAMESPACE_ITUNES, 'explicit')) { foreach ($ratings as $rating) { $rating_scheme = 'urn:itunes'; $rating_value = null; if (isset($rating['data'])) { $rating_value = $this->sanitize($rating['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } $ratings_parent[] = $this->registry->create(Rating::class, [$rating_scheme, $rating_value]); } } if (is_array($ratings_parent)) { $ratings_parent = array_values(array_unique($ratings_parent)); } // RESTRICTIONS if ($restrictions = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'restriction')) { foreach ($restrictions as $restriction) { $restriction_relationship = null; $restriction_type = null; $restriction_value = null; if (isset($restriction['attribs']['']['relationship'])) { $restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($restriction['attribs']['']['type'])) { $restriction_type = $this->sanitize($restriction['attribs']['']['type'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($restriction['data'])) { $restriction_value = $this->sanitize($restriction['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } $restrictions_parent[] = $this->registry->create(Restriction::class, [$restriction_relationship, $restriction_type, $restriction_value]); } } elseif ($restrictions = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ITUNES, 'block')) { foreach ($restrictions as $restriction) { $restriction_relationship = Restriction::RELATIONSHIP_ALLOW; $restriction_type = null; $restriction_value = 'itunes'; if (isset($restriction['data']) && strtolower($restriction['data']) === 'yes') { $restriction_relationship = Restriction::RELATIONSHIP_DENY; } $restrictions_parent[] = $this->registry->create(Restriction::class, [$restriction_relationship, $restriction_type, $restriction_value]); } } elseif ($restrictions = $parent->get_channel_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'restriction')) { foreach ($restrictions as $restriction) { $restriction_relationship = null; $restriction_type = null; $restriction_value = null; if (isset($restriction['attribs']['']['relationship'])) { $restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($restriction['attribs']['']['type'])) { $restriction_type = $this->sanitize($restriction['attribs']['']['type'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($restriction['data'])) { $restriction_value = $this->sanitize($restriction['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } $restrictions_parent[] = $this->registry->create(Restriction::class, [$restriction_relationship, $restriction_type, $restriction_value]); } } elseif ($restrictions = $parent->get_channel_tags(\SimplePie\SimplePie::NAMESPACE_ITUNES, 'block')) { foreach ($restrictions as $restriction) { $restriction_relationship = Restriction::RELATIONSHIP_ALLOW; $restriction_type = null; $restriction_value = 'itunes'; if (isset($restriction['data']) && strtolower($restriction['data']) === 'yes') { $restriction_relationship = Restriction::RELATIONSHIP_DENY; } $restrictions_parent[] = $this->registry->create(Restriction::class, [$restriction_relationship, $restriction_type, $restriction_value]); } } if (count($restrictions_parent) > 0) { $restrictions_parent = array_values(array_unique($restrictions_parent)); } else { $restrictions_parent = [new \SimplePie\Restriction(Restriction::RELATIONSHIP_ALLOW, null, 'default')]; } // THUMBNAILS if ($thumbnails = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'thumbnail')) { foreach ($thumbnails as $thumbnail) { if (isset($thumbnail['attribs']['']['url'])) { $thumbnails_parent[] = $this->sanitize($thumbnail['attribs']['']['url'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($thumbnail)); } } } elseif ($thumbnails = $parent->get_channel_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'thumbnail')) { foreach ($thumbnails as $thumbnail) { if (isset($thumbnail['attribs']['']['url'])) { $thumbnails_parent[] = $this->sanitize($thumbnail['attribs']['']['url'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($thumbnail)); } } } // TITLES if ($title_parent = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'title')) { if (isset($title_parent[0]['data'])) { $title_parent = $this->sanitize($title_parent[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } } elseif ($title_parent = $parent->get_channel_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'title')) { if (isset($title_parent[0]['data'])) { $title_parent = $this->sanitize($title_parent[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } } // Clear the memory unset($parent); // Attributes $bitrate = null; $channels = null; $duration = null; $expression = null; $framerate = null; $height = null; $javascript = null; $lang = null; $length = null; $medium = null; $samplingrate = null; $type = null; $url = null; $width = null; // Elements $captions = null; $categories = null; $copyrights = null; $credits = null; $description = null; $hashes = null; $keywords = null; $player = null; $ratings = null; $restrictions = null; $thumbnails = null; $title = null; // If we have media:group tags, loop through them. foreach ((array) $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'group') as $group) { if (isset($group['child']) && isset($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['content'])) { // If we have media:content tags, loop through them. foreach ((array) $group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['content'] as $content) { if (isset($content['attribs']['']['url'])) { // Attributes $bitrate = null; $channels = null; $duration = null; $expression = null; $framerate = null; $height = null; $javascript = null; $lang = null; $length = null; $medium = null; $samplingrate = null; $type = null; $url = null; $width = null; // Elements $captions = null; $categories = null; $copyrights = null; $credits = null; $description = null; $hashes = null; $keywords = null; $player = null; $ratings = null; $restrictions = null; $thumbnails = null; $title = null; // Start checking the attributes of media:content if (isset($content['attribs']['']['bitrate'])) { $bitrate = $this->sanitize($content['attribs']['']['bitrate'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($content['attribs']['']['channels'])) { $channels = $this->sanitize($content['attribs']['']['channels'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($content['attribs']['']['duration'])) { $duration = $this->sanitize($content['attribs']['']['duration'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } else { $duration = $duration_parent; } if (isset($content['attribs']['']['expression'])) { $expression = $this->sanitize($content['attribs']['']['expression'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($content['attribs']['']['framerate'])) { $framerate = $this->sanitize($content['attribs']['']['framerate'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($content['attribs']['']['height'])) { $height = $this->sanitize($content['attribs']['']['height'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($content['attribs']['']['lang'])) { $lang = $this->sanitize($content['attribs']['']['lang'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($content['attribs']['']['fileSize'])) { $length = intval($content['attribs']['']['fileSize']); } if (isset($content['attribs']['']['medium'])) { $medium = $this->sanitize($content['attribs']['']['medium'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($content['attribs']['']['samplingrate'])) { $samplingrate = $this->sanitize($content['attribs']['']['samplingrate'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($content['attribs']['']['type'])) { $type = $this->sanitize($content['attribs']['']['type'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($content['attribs']['']['width'])) { $width = $this->sanitize($content['attribs']['']['width'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } $url = $this->sanitize($content['attribs']['']['url'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($content)); // Checking the other optional media: elements. Priority: media:content, media:group, item, channel // CAPTIONS if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['text'])) { foreach ($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['text'] as $caption) { $caption_type = null; $caption_lang = null; $caption_startTime = null; $caption_endTime = null; $caption_text = null; if (isset($caption['attribs']['']['type'])) { $caption_type = $this->sanitize($caption['attribs']['']['type'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($caption['attribs']['']['lang'])) { $caption_lang = $this->sanitize($caption['attribs']['']['lang'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($caption['attribs']['']['start'])) { $caption_startTime = $this->sanitize($caption['attribs']['']['start'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($caption['attribs']['']['end'])) { $caption_endTime = $this->sanitize($caption['attribs']['']['end'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($caption['data'])) { $caption_text = $this->sanitize($caption['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } $captions[] = $this->registry->create(Caption::class, [$caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text]); } if (is_array($captions)) { $captions = array_values(array_unique($captions)); } } elseif (isset($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['text'])) { foreach ($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['text'] as $caption) { $caption_type = null; $caption_lang = null; $caption_startTime = null; $caption_endTime = null; $caption_text = null; if (isset($caption['attribs']['']['type'])) { $caption_type = $this->sanitize($caption['attribs']['']['type'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($caption['attribs']['']['lang'])) { $caption_lang = $this->sanitize($caption['attribs']['']['lang'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($caption['attribs']['']['start'])) { $caption_startTime = $this->sanitize($caption['attribs']['']['start'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($caption['attribs']['']['end'])) { $caption_endTime = $this->sanitize($caption['attribs']['']['end'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($caption['data'])) { $caption_text = $this->sanitize($caption['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } $captions[] = $this->registry->create(Caption::class, [$caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text]); } if (is_array($captions)) { $captions = array_values(array_unique($captions)); } } else { $captions = $captions_parent; } // CATEGORIES if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['category'])) { foreach ((array) $content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['category'] as $category) { $term = null; $scheme = null; $label = null; if (isset($category['data'])) { $term = $this->sanitize($category['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($category['attribs']['']['scheme'])) { $scheme = $this->sanitize($category['attribs']['']['scheme'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } else { $scheme = 'http://search.yahoo.com/mrss/category_schema'; } if (isset($category['attribs']['']['label'])) { $label = $this->sanitize($category['attribs']['']['label'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } $categories[] = $this->registry->create(Category::class, [$term, $scheme, $label]); } } if (isset($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['category'])) { foreach ((array) $group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['category'] as $category) { $term = null; $scheme = null; $label = null; if (isset($category['data'])) { $term = $this->sanitize($category['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($category['attribs']['']['scheme'])) { $scheme = $this->sanitize($category['attribs']['']['scheme'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } else { $scheme = 'http://search.yahoo.com/mrss/category_schema'; } if (isset($category['attribs']['']['label'])) { $label = $this->sanitize($category['attribs']['']['label'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } $categories[] = $this->registry->create(Category::class, [$term, $scheme, $label]); } } if (is_array($categories) && is_array($categories_parent)) { $categories = array_values(array_unique(array_merge($categories, $categories_parent))); } elseif (is_array($categories)) { $categories = array_values(array_unique($categories)); } elseif (is_array($categories_parent)) { $categories = array_values(array_unique($categories_parent)); } // COPYRIGHTS if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['copyright'])) { $copyright_url = null; $copyright_label = null; if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'])) { $copyright_url = $this->sanitize($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['copyright'][0]['data'])) { $copyright_label = $this->sanitize($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['copyright'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } $copyrights = $this->registry->create(Copyright::class, [$copyright_url, $copyright_label]); } elseif (isset($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['copyright'])) { $copyright_url = null; $copyright_label = null; if (isset($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'])) { $copyright_url = $this->sanitize($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['copyright'][0]['data'])) { $copyright_label = $this->sanitize($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['copyright'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } $copyrights = $this->registry->create(Copyright::class, [$copyright_url, $copyright_label]); } else { $copyrights = $copyrights_parent; } // CREDITS if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['credit'])) { foreach ($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['credit'] as $credit) { $credit_role = null; $credit_scheme = null; $credit_name = null; if (isset($credit['attribs']['']['role'])) { $credit_role = $this->sanitize($credit['attribs']['']['role'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($credit['attribs']['']['scheme'])) { $credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } else { $credit_scheme = 'urn:ebu'; } if (isset($credit['data'])) { $credit_name = $this->sanitize($credit['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } $credits[] = $this->registry->create(Credit::class, [$credit_role, $credit_scheme, $credit_name]); } if (is_array($credits)) { $credits = array_values(array_unique($credits)); } } elseif (isset($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['credit'])) { foreach ($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['credit'] as $credit) { $credit_role = null; $credit_scheme = null; $credit_name = null; if (isset($credit['attribs']['']['role'])) { $credit_role = $this->sanitize($credit['attribs']['']['role'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($credit['attribs']['']['scheme'])) { $credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } else { $credit_scheme = 'urn:ebu'; } if (isset($credit['data'])) { $credit_name = $this->sanitize($credit['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } $credits[] = $this->registry->create(Credit::class, [$credit_role, $credit_scheme, $credit_name]); } if (is_array($credits)) { $credits = array_values(array_unique($credits)); } } else { $credits = $credits_parent; } // DESCRIPTION if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['description'])) { $description = $this->sanitize($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['description'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } elseif (isset($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['description'])) { $description = $this->sanitize($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['description'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } else { $description = $description_parent; } // HASHES if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['hash'])) { foreach ($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['hash'] as $hash) { $value = null; $algo = null; if (isset($hash['data'])) { $value = $this->sanitize($hash['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($hash['attribs']['']['algo'])) { $algo = $this->sanitize($hash['attribs']['']['algo'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } else { $algo = 'md5'; } $hashes[] = $algo.':'.$value; } if (is_array($hashes)) { $hashes = array_values(array_unique($hashes)); } } elseif (isset($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['hash'])) { foreach ($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['hash'] as $hash) { $value = null; $algo = null; if (isset($hash['data'])) { $value = $this->sanitize($hash['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($hash['attribs']['']['algo'])) { $algo = $this->sanitize($hash['attribs']['']['algo'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } else { $algo = 'md5'; } $hashes[] = $algo.':'.$value; } if (is_array($hashes)) { $hashes = array_values(array_unique($hashes)); } } else { $hashes = $hashes_parent; } // KEYWORDS if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['keywords'])) { if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['keywords'][0]['data'])) { $temp = explode(',', $this->sanitize($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['keywords'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT)); foreach ($temp as $word) { $keywords[] = trim($word); } unset($temp); } if (is_array($keywords)) { $keywords = array_values(array_unique($keywords)); } } elseif (isset($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['keywords'])) { if (isset($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['keywords'][0]['data'])) { $temp = explode(',', $this->sanitize($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['keywords'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT)); foreach ($temp as $word) { $keywords[] = trim($word); } unset($temp); } if (is_array($keywords)) { $keywords = array_values(array_unique($keywords)); } } else { $keywords = $keywords_parent; } // PLAYER if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['player'])) { $playerElem = $content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['player'][0]; $player = $this->sanitize($playerElem['attribs']['']['url'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($playerElem)); } elseif (isset($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['player'])) { $playerElem = $group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['player'][0]; $player = $this->sanitize($playerElem['attribs']['']['url'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($playerElem)); } else { $player = $player_parent; } // RATINGS if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['rating'])) { foreach ($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['rating'] as $rating) { $rating_scheme = null; $rating_value = null; if (isset($rating['attribs']['']['scheme'])) { $rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } else { $rating_scheme = 'urn:simple'; } if (isset($rating['data'])) { $rating_value = $this->sanitize($rating['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } $ratings[] = $this->registry->create(Rating::class, [$rating_scheme, $rating_value]); } if (is_array($ratings)) { $ratings = array_values(array_unique($ratings)); } } elseif (isset($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['rating'])) { foreach ($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['rating'] as $rating) { $rating_scheme = null; $rating_value = null; if (isset($rating['attribs']['']['scheme'])) { $rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } else { $rating_scheme = 'urn:simple'; } if (isset($rating['data'])) { $rating_value = $this->sanitize($rating['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } $ratings[] = $this->registry->create(Rating::class, [$rating_scheme, $rating_value]); } if (is_array($ratings)) { $ratings = array_values(array_unique($ratings)); } } else { $ratings = $ratings_parent; } // RESTRICTIONS if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['restriction'])) { foreach ($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['restriction'] as $restriction) { $restriction_relationship = null; $restriction_type = null; $restriction_value = null; if (isset($restriction['attribs']['']['relationship'])) { $restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($restriction['attribs']['']['type'])) { $restriction_type = $this->sanitize($restriction['attribs']['']['type'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($restriction['data'])) { $restriction_value = $this->sanitize($restriction['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } $restrictions[] = $this->registry->create(Restriction::class, [$restriction_relationship, $restriction_type, $restriction_value]); } if (is_array($restrictions)) { $restrictions = array_values(array_unique($restrictions)); } } elseif (isset($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['restriction'])) { foreach ($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['restriction'] as $restriction) { $restriction_relationship = null; $restriction_type = null; $restriction_value = null; if (isset($restriction['attribs']['']['relationship'])) { $restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($restriction['attribs']['']['type'])) { $restriction_type = $this->sanitize($restriction['attribs']['']['type'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($restriction['data'])) { $restriction_value = $this->sanitize($restriction['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } $restrictions[] = $this->registry->create(Restriction::class, [$restriction_relationship, $restriction_type, $restriction_value]); } if (is_array($restrictions)) { $restrictions = array_values(array_unique($restrictions)); } } else { $restrictions = $restrictions_parent; } // THUMBNAILS if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['thumbnail'])) { foreach ($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['thumbnail'] as $thumbnail) { $thumbnails[] = $this->sanitize($thumbnail['attribs']['']['url'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($thumbnail)); } if (is_array($thumbnails)) { $thumbnails = array_values(array_unique($thumbnails)); } } elseif (isset($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['thumbnail'])) { foreach ($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['thumbnail'] as $thumbnail) { $thumbnails[] = $this->sanitize($thumbnail['attribs']['']['url'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($thumbnail)); } if (is_array($thumbnails)) { $thumbnails = array_values(array_unique($thumbnails)); } } else { $thumbnails = $thumbnails_parent; } // TITLES if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['title'])) { $title = $this->sanitize($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['title'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } elseif (isset($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['title'])) { $title = $this->sanitize($group['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['title'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } else { $title = $title_parent; } $this->data['enclosures'][] = $this->registry->create(Enclosure::class, [$url, $type, $length, null, $bitrate, $captions, $categories, $channels, $copyrights, $credits, $description, $duration, $expression, $framerate, $hashes, $height, $keywords, $lang, $medium, $player, $ratings, $restrictions, $samplingrate, $thumbnails, $title, $width]); } } } } // If we have standalone media:content tags, loop through them. if (isset($this->data['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['content'])) { foreach ((array) $this->data['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['content'] as $content) { if (isset($content['attribs']['']['url']) || isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['player'])) { // Attributes $bitrate = null; $channels = null; $duration = null; $expression = null; $framerate = null; $height = null; $javascript = null; $lang = null; $length = null; $medium = null; $samplingrate = null; $type = null; $url = null; $width = null; // Elements $captions = null; $categories = null; $copyrights = null; $credits = null; $description = null; $hashes = null; $keywords = null; $player = null; $ratings = null; $restrictions = null; $thumbnails = null; $title = null; // Start checking the attributes of media:content if (isset($content['attribs']['']['bitrate'])) { $bitrate = $this->sanitize($content['attribs']['']['bitrate'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($content['attribs']['']['channels'])) { $channels = $this->sanitize($content['attribs']['']['channels'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($content['attribs']['']['duration'])) { $duration = $this->sanitize($content['attribs']['']['duration'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } else { $duration = $duration_parent; } if (isset($content['attribs']['']['expression'])) { $expression = $this->sanitize($content['attribs']['']['expression'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($content['attribs']['']['framerate'])) { $framerate = $this->sanitize($content['attribs']['']['framerate'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($content['attribs']['']['height'])) { $height = $this->sanitize($content['attribs']['']['height'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($content['attribs']['']['lang'])) { $lang = $this->sanitize($content['attribs']['']['lang'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($content['attribs']['']['fileSize'])) { $length = intval($content['attribs']['']['fileSize']); } if (isset($content['attribs']['']['medium'])) { $medium = $this->sanitize($content['attribs']['']['medium'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($content['attribs']['']['samplingrate'])) { $samplingrate = $this->sanitize($content['attribs']['']['samplingrate'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($content['attribs']['']['type'])) { $type = $this->sanitize($content['attribs']['']['type'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($content['attribs']['']['width'])) { $width = $this->sanitize($content['attribs']['']['width'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($content['attribs']['']['url'])) { $url = $this->sanitize($content['attribs']['']['url'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($content)); } // Checking the other optional media: elements. Priority: media:content, media:group, item, channel // CAPTIONS if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['text'])) { foreach ($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['text'] as $caption) { $caption_type = null; $caption_lang = null; $caption_startTime = null; $caption_endTime = null; $caption_text = null; if (isset($caption['attribs']['']['type'])) { $caption_type = $this->sanitize($caption['attribs']['']['type'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($caption['attribs']['']['lang'])) { $caption_lang = $this->sanitize($caption['attribs']['']['lang'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($caption['attribs']['']['start'])) { $caption_startTime = $this->sanitize($caption['attribs']['']['start'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($caption['attribs']['']['end'])) { $caption_endTime = $this->sanitize($caption['attribs']['']['end'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($caption['data'])) { $caption_text = $this->sanitize($caption['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } $captions[] = $this->registry->create(Caption::class, [$caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text]); } if (is_array($captions)) { $captions = array_values(array_unique($captions)); } } else { $captions = $captions_parent; } // CATEGORIES if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['category'])) { foreach ((array) $content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['category'] as $category) { $term = null; $scheme = null; $label = null; if (isset($category['data'])) { $term = $this->sanitize($category['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($category['attribs']['']['scheme'])) { $scheme = $this->sanitize($category['attribs']['']['scheme'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } else { $scheme = 'http://search.yahoo.com/mrss/category_schema'; } if (isset($category['attribs']['']['label'])) { $label = $this->sanitize($category['attribs']['']['label'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } $categories[] = $this->registry->create(Category::class, [$term, $scheme, $label]); } } if (is_array($categories) && is_array($categories_parent)) { $categories = array_values(array_unique(array_merge($categories, $categories_parent))); } elseif (is_array($categories)) { $categories = array_values(array_unique($categories)); } elseif (is_array($categories_parent)) { $categories = array_values(array_unique($categories_parent)); } else { $categories = null; } // COPYRIGHTS if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['copyright'])) { $copyright_url = null; $copyright_label = null; if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'])) { $copyright_url = $this->sanitize($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['copyright'][0]['data'])) { $copyright_label = $this->sanitize($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['copyright'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } $copyrights = $this->registry->create(Copyright::class, [$copyright_url, $copyright_label]); } else { $copyrights = $copyrights_parent; } // CREDITS if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['credit'])) { foreach ($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['credit'] as $credit) { $credit_role = null; $credit_scheme = null; $credit_name = null; if (isset($credit['attribs']['']['role'])) { $credit_role = $this->sanitize($credit['attribs']['']['role'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($credit['attribs']['']['scheme'])) { $credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } else { $credit_scheme = 'urn:ebu'; } if (isset($credit['data'])) { $credit_name = $this->sanitize($credit['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } $credits[] = $this->registry->create(Credit::class, [$credit_role, $credit_scheme, $credit_name]); } if (is_array($credits)) { $credits = array_values(array_unique($credits)); } } else { $credits = $credits_parent; } // DESCRIPTION if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['description'])) { $description = $this->sanitize($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['description'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } else { $description = $description_parent; } // HASHES if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['hash'])) { foreach ($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['hash'] as $hash) { $value = null; $algo = null; if (isset($hash['data'])) { $value = $this->sanitize($hash['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($hash['attribs']['']['algo'])) { $algo = $this->sanitize($hash['attribs']['']['algo'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } else { $algo = 'md5'; } $hashes[] = $algo.':'.$value; } if (is_array($hashes)) { $hashes = array_values(array_unique($hashes)); } } else { $hashes = $hashes_parent; } // KEYWORDS if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['keywords'])) { if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['keywords'][0]['data'])) { $temp = explode(',', $this->sanitize($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['keywords'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT)); foreach ($temp as $word) { $keywords[] = trim($word); } unset($temp); } if (is_array($keywords)) { $keywords = array_values(array_unique($keywords)); } } else { $keywords = $keywords_parent; } // PLAYER if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['player'])) { if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['player'][0]['attribs']['']['url'])) { $playerElem = $content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['player'][0]; $player = $this->sanitize($playerElem['attribs']['']['url'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($playerElem)); } } else { $player = $player_parent; } // RATINGS if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['rating'])) { foreach ($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['rating'] as $rating) { $rating_scheme = null; $rating_value = null; if (isset($rating['attribs']['']['scheme'])) { $rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } else { $rating_scheme = 'urn:simple'; } if (isset($rating['data'])) { $rating_value = $this->sanitize($rating['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } $ratings[] = $this->registry->create(Rating::class, [$rating_scheme, $rating_value]); } if (is_array($ratings)) { $ratings = array_values(array_unique($ratings)); } } else { $ratings = $ratings_parent; } // RESTRICTIONS if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['restriction'])) { foreach ($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['restriction'] as $restriction) { $restriction_relationship = null; $restriction_type = null; $restriction_value = null; if (isset($restriction['attribs']['']['relationship'])) { $restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($restriction['attribs']['']['type'])) { $restriction_type = $this->sanitize($restriction['attribs']['']['type'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($restriction['data'])) { $restriction_value = $this->sanitize($restriction['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } $restrictions[] = $this->registry->create(Restriction::class, [$restriction_relationship, $restriction_type, $restriction_value]); } if (is_array($restrictions)) { $restrictions = array_values(array_unique($restrictions)); } } else { $restrictions = $restrictions_parent; } // THUMBNAILS if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['thumbnail'])) { foreach ($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['thumbnail'] as $thumbnail) { if (isset($thumbnail['attribs']['']['url'])) { $thumbnails[] = $this->sanitize($thumbnail['attribs']['']['url'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($thumbnail)); } } if (is_array($thumbnails)) { $thumbnails = array_values(array_unique($thumbnails)); } } else { $thumbnails = $thumbnails_parent; } // TITLES if (isset($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['title'])) { $title = $this->sanitize($content['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['title'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } else { $title = $title_parent; } $this->data['enclosures'][] = $this->registry->create(Enclosure::class, [$url, $type, $length, null, $bitrate, $captions, $categories, $channels, $copyrights, $credits, $description, $duration, $expression, $framerate, $hashes, $height, $keywords, $lang, $medium, $player, $ratings, $restrictions, $samplingrate, $thumbnails, $title, $width]); } } } foreach ((array) $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'link') as $link) { if (isset($link['attribs']['']['href']) && !empty($link['attribs']['']['rel']) && $link['attribs']['']['rel'] === 'enclosure') { // Attributes $bitrate = null; $channels = null; $duration = null; $expression = null; $framerate = null; $height = null; $javascript = null; $lang = null; $length = null; $medium = null; $samplingrate = null; $type = null; $url = null; $width = null; $url = $this->sanitize($link['attribs']['']['href'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($link)); if (isset($link['attribs']['']['type'])) { $type = $this->sanitize($link['attribs']['']['type'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($link['attribs']['']['length'])) { $length = intval($link['attribs']['']['length']); } if (isset($link['attribs']['']['title'])) { $title = $this->sanitize($link['attribs']['']['title'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } else { $title = $title_parent; } // Since we don't have group or content for these, we'll just pass the '*_parent' variables directly to the constructor $this->data['enclosures'][] = $this->registry->create(Enclosure::class, [$url, $type, $length, null, $bitrate, $captions_parent, $categories_parent, $channels, $copyrights_parent, $credits_parent, $description_parent, $duration_parent, $expression, $framerate, $hashes_parent, $height, $keywords_parent, $lang, $medium, $player_parent, $ratings_parent, $restrictions_parent, $samplingrate, $thumbnails_parent, $title, $width]); } } foreach ((array) $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_03, 'link') as $link) { if (isset($link['attribs']['']['href']) && !empty($link['attribs']['']['rel']) && $link['attribs']['']['rel'] === 'enclosure') { // Attributes $bitrate = null; $channels = null; $duration = null; $expression = null; $framerate = null; $height = null; $javascript = null; $lang = null; $length = null; $medium = null; $samplingrate = null; $type = null; $url = null; $width = null; $url = $this->sanitize($link['attribs']['']['href'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($link)); if (isset($link['attribs']['']['type'])) { $type = $this->sanitize($link['attribs']['']['type'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($link['attribs']['']['length'])) { $length = intval($link['attribs']['']['length']); } // Since we don't have group or content for these, we'll just pass the '*_parent' variables directly to the constructor $this->data['enclosures'][] = $this->registry->create(Enclosure::class, [$url, $type, $length, null, $bitrate, $captions_parent, $categories_parent, $channels, $copyrights_parent, $credits_parent, $description_parent, $duration_parent, $expression, $framerate, $hashes_parent, $height, $keywords_parent, $lang, $medium, $player_parent, $ratings_parent, $restrictions_parent, $samplingrate, $thumbnails_parent, $title_parent, $width]); } } foreach ($this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_RSS_20, 'enclosure') ?? [] as $enclosure) { if (isset($enclosure['attribs']['']['url'])) { // Attributes $bitrate = null; $channels = null; $duration = null; $expression = null; $framerate = null; $height = null; $javascript = null; $lang = null; $length = null; $medium = null; $samplingrate = null; $type = null; $url = null; $width = null; $url = $this->sanitize($enclosure['attribs']['']['url'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_own_base($enclosure)); $url = $this->get_sanitize()->https_url($url); if (isset($enclosure['attribs']['']['type'])) { $type = $this->sanitize($enclosure['attribs']['']['type'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($enclosure['attribs']['']['length'])) { $length = intval($enclosure['attribs']['']['length']); } // Since we don't have group or content for these, we'll just pass the '*_parent' variables directly to the constructor $this->data['enclosures'][] = $this->registry->create(Enclosure::class, [$url, $type, $length, null, $bitrate, $captions_parent, $categories_parent, $channels, $copyrights_parent, $credits_parent, $description_parent, $duration_parent, $expression, $framerate, $hashes_parent, $height, $keywords_parent, $lang, $medium, $player_parent, $ratings_parent, $restrictions_parent, $samplingrate, $thumbnails_parent, $title_parent, $width]); } } if (count($this->data['enclosures']) === 0 && ($url || $type || $length || $bitrate || $captions_parent || $categories_parent || $channels || $copyrights_parent || $credits_parent || $description_parent || $duration_parent || $expression || $framerate || $hashes_parent || $height || $keywords_parent || $lang || $medium || $player_parent || $ratings_parent || $samplingrate || $thumbnails_parent || $title_parent || $width)) { // Since we don't have group or content for these, we'll just pass the '*_parent' variables directly to the constructor $this->data['enclosures'][] = $this->registry->create(Enclosure::class, [$url, $type, $length, null, $bitrate, $captions_parent, $categories_parent, $channels, $copyrights_parent, $credits_parent, $description_parent, $duration_parent, $expression, $framerate, $hashes_parent, $height, $keywords_parent, $lang, $medium, $player_parent, $ratings_parent, $restrictions_parent, $samplingrate, $thumbnails_parent, $title_parent, $width]); } $this->data['enclosures'] = array_values(array_unique($this->data['enclosures'])); } if (!empty($this->data['enclosures'])) { return $this->data['enclosures']; } return null; } /** * Get the latitude coordinates for the item * * Compatible with the W3C WGS84 Basic Geo and GeoRSS specifications * * Uses `<geo:lat>` or `<georss:point>` * * @since 1.0 * @link http://www.w3.org/2003/01/geo/ W3C WGS84 Basic Geo * @link http://www.georss.org/ GeoRSS * @return float|null */ public function get_latitude() { if ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_W3C_BASIC_GEO, 'lat')) { return (float) $return[0]['data']; } elseif (($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match)) { return (float) $match[1]; } return null; } /** * Get the longitude coordinates for the item * * Compatible with the W3C WGS84 Basic Geo and GeoRSS specifications * * Uses `<geo:long>`, `<geo:lon>` or `<georss:point>` * * @since 1.0 * @link http://www.w3.org/2003/01/geo/ W3C WGS84 Basic Geo * @link http://www.georss.org/ GeoRSS * @return float|null */ public function get_longitude() { if ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_W3C_BASIC_GEO, 'long')) { return (float) $return[0]['data']; } elseif ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_W3C_BASIC_GEO, 'lon')) { return (float) $return[0]['data']; } elseif (($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match)) { return (float) $match[2]; } return null; } /** * Get the `<atom:source>` for the item * * @since 1.1 * @return \SimplePie\Source|null */ public function get_source() { if ($return = $this->get_item_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'source')) { return $this->registry->create(Source::class, [$this, $return[0]]); } return null; } public function set_sanitize(Sanitize $sanitize): void { $this->sanitize = $sanitize; } protected function get_sanitize(): Sanitize { if ($this->sanitize === null) { $this->sanitize = new Sanitize(); } return $this->sanitize; } } class_alias('SimplePie\Item', 'SimplePie_Item'); �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������IRI.php���������������������������������������������������������������������������������������������0000644�����������������00000103767�15250524467�0005730 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php // SPDX-FileCopyrightText: 2004-2023 Ryan Parman, Sam Sneddon, Ryan McCue // SPDX-FileCopyrightText: 2008 Steve Minutillo // SPDX-License-Identifier: BSD-3-Clause declare(strict_types=1); namespace SimplePie; /** * IRI parser/serialiser/normaliser * * @property ?string $scheme * @property ?string $userinfo * @property ?string $host * @property ?int $port * @property-write int|string|null $port * @property ?string $authority * @property string $path * @property ?string $query * @property ?string $fragment */ class IRI { /** * Scheme * * @var ?string */ protected $scheme = null; /** * User Information * * @var ?string */ protected $iuserinfo = null; /** * ihost * * @var ?string */ protected $ihost = null; /** * Port * * @var ?int */ protected $port = null; /** * ipath * * @var string */ protected $ipath = ''; /** * iquery * * @var ?string */ protected $iquery = null; /** * ifragment * * @var ?string */ protected $ifragment = null; /** * Normalization database * * Each key is the scheme, each value is an array with each key as the IRI * part and value as the default value for that part. * * @var array<string, array<string, mixed>> */ protected $normalization = [ 'acap' => [ 'port' => 674 ], 'dict' => [ 'port' => 2628 ], 'file' => [ 'ihost' => 'localhost' ], 'http' => [ 'port' => 80, 'ipath' => '/' ], 'https' => [ 'port' => 443, 'ipath' => '/' ], ]; /** * Return the entire IRI when you try and read the object as a string * * @return string */ public function __toString() { return (string) $this->get_iri(); } /** * Overload __set() to provide access via properties * * @param string $name Property name * @param mixed $value Property value * @return void */ public function __set(string $name, $value) { $callable = [$this, 'set_' . $name]; if (is_callable($callable)) { call_user_func($callable, $value); } elseif ( $name === 'iauthority' || $name === 'iuserinfo' || $name === 'ihost' || $name === 'ipath' || $name === 'iquery' || $name === 'ifragment' ) { call_user_func([$this, 'set_' . substr($name, 1)], $value); } } /** * Overload __get() to provide access via properties * * @param string $name Property name * @return mixed */ public function __get(string $name) { // isset() returns false for null, we don't want to do that // Also why we use array_key_exists below instead of isset() $props = get_object_vars($this); if ( $name === 'iri' || $name === 'uri' || $name === 'iauthority' || $name === 'authority' ) { $return = $this->{"get_$name"}(); } elseif (array_key_exists($name, $props)) { $return = $this->$name; } // host -> ihost elseif (array_key_exists($prop = 'i' . $name, $props)) { $name = $prop; $return = $this->$prop; } // ischeme -> scheme elseif (($prop = substr($name, 1)) && array_key_exists($prop, $props)) { $name = $prop; $return = $this->$prop; } else { trigger_error('Undefined property: ' . get_class($this) . '::' . $name, E_USER_NOTICE); $return = null; } if ($return === null && isset($this->scheme, $this->normalization[$this->scheme][$name])) { return $this->normalization[$this->scheme][$name]; } return $return; } /** * Overload __isset() to provide access via properties * * @param string $name Property name * @return bool */ public function __isset(string $name) { return method_exists($this, 'get_' . $name) || isset($this->$name); } /** * Overload __unset() to provide access via properties * * @param string $name Property name * @return void */ public function __unset(string $name) { $callable = [$this, 'set_' . $name]; if (is_callable($callable)) { call_user_func($callable, ''); } } /** * Create a new IRI object, from a specified string * * @param string|null $iri */ public function __construct(?string $iri = null) { $this->set_iri($iri); } /** * Clean up * @return void */ public function __destruct() { $this->set_iri(null, true); $this->set_path(null, true); $this->set_authority(null, true); } /** * Create a new IRI object by resolving a relative IRI * * Returns false if $base is not absolute, otherwise an IRI. * * @param IRI|string $base (Absolute) Base IRI * @param IRI|string $relative Relative IRI * @return IRI|false */ public static function absolutize($base, $relative) { if (!($relative instanceof IRI)) { $relative = new IRI($relative); } if (!$relative->is_valid()) { return false; } elseif ($relative->scheme !== null) { return clone $relative; } else { if (!($base instanceof IRI)) { $base = new IRI($base); } if ($base->scheme !== null && $base->is_valid()) { if ($relative->get_iri() !== '') { if ($relative->iuserinfo !== null || $relative->ihost !== null || $relative->port !== null) { $target = clone $relative; $target->scheme = $base->scheme; } else { $target = new IRI(); $target->scheme = $base->scheme; $target->iuserinfo = $base->iuserinfo; $target->ihost = $base->ihost; $target->port = $base->port; if ($relative->ipath !== '') { if ($relative->ipath[0] === '/') { $target->ipath = $relative->ipath; } elseif (($base->iuserinfo !== null || $base->ihost !== null || $base->port !== null) && $base->ipath === '') { $target->ipath = '/' . $relative->ipath; } elseif (($last_segment = strrpos($base->ipath, '/')) !== false) { $target->ipath = substr($base->ipath, 0, $last_segment + 1) . $relative->ipath; } else { $target->ipath = $relative->ipath; } $target->ipath = $target->remove_dot_segments($target->ipath); $target->iquery = $relative->iquery; } else { $target->ipath = $base->ipath; if ($relative->iquery !== null) { $target->iquery = $relative->iquery; } elseif ($base->iquery !== null) { $target->iquery = $base->iquery; } } $target->ifragment = $relative->ifragment; } } else { $target = clone $base; $target->ifragment = null; } $target->scheme_normalization(); return $target; } return false; } } /** * Parse an IRI into scheme/authority/path/query/fragment segments * * @param string $iri * @return array{ * scheme: string|null, * authority: string|null, * path: string, * query: string|null, * fragment: string|null, * }|false */ protected function parse_iri(string $iri) { $iri = trim($iri, "\x20\x09\x0A\x0C\x0D"); if (preg_match('/^(?:(?P<scheme>[^:\/?#]+):)?(:?\/\/(?P<authority>[^\/?#]*))?(?P<path>[^?#]*)(?:\?(?P<query>[^#]*))?(?:#(?P<fragment>.*))?$/', $iri, $match, \PREG_UNMATCHED_AS_NULL)) { // TODO: Remove once we require PHP ≥ 7.4. $match['query'] = $match['query'] ?? null; $match['fragment'] = $match['fragment'] ?? null; return $match; } // This can occur when a paragraph is accidentally parsed as a URI return false; } /** * Remove dot segments from a path * * @param string $input * @return string */ protected function remove_dot_segments(string $input) { $output = ''; while (strpos($input, './') !== false || strpos($input, '/.') !== false || $input === '.' || $input === '..') { // A: If the input buffer begins with a prefix of "../" or "./", then remove that prefix from the input buffer; otherwise, if (strpos($input, '../') === 0) { $input = substr($input, 3); } elseif (strpos($input, './') === 0) { $input = substr($input, 2); } // B: if the input buffer begins with a prefix of "/./" or "/.", where "." is a complete path segment, then replace that prefix with "/" in the input buffer; otherwise, elseif (strpos($input, '/./') === 0) { $input = substr($input, 2); } elseif ($input === '/.') { $input = '/'; } // C: if the input buffer begins with a prefix of "/../" or "/..", where ".." is a complete path segment, then replace that prefix with "/" in the input buffer and remove the last segment and its preceding "/" (if any) from the output buffer; otherwise, elseif (strpos($input, '/../') === 0) { $input = substr($input, 3); $output = substr_replace($output, '', intval(strrpos($output, '/'))); } elseif ($input === '/..') { $input = '/'; $output = substr_replace($output, '', intval(strrpos($output, '/'))); } // D: if the input buffer consists only of "." or "..", then remove that from the input buffer; otherwise, elseif ($input === '.' || $input === '..') { $input = ''; } // E: move the first path segment in the input buffer to the end of the output buffer, including the initial "/" character (if any) and any subsequent characters up to, but not including, the next "/" character or the end of the input buffer elseif (($pos = strpos($input, '/', 1)) !== false) { $output .= substr($input, 0, $pos); $input = substr_replace($input, '', 0, $pos); } else { $output .= $input; $input = ''; } } return $output . $input; } /** * Replace invalid character with percent encoding * * @param string $string Input string * @param string $extra_chars Valid characters not in iunreserved or * iprivate (this is ASCII-only) * @param bool $iprivate Allow iprivate * @return string */ protected function replace_invalid_with_pct_encoding(string $string, string $extra_chars, bool $iprivate = false) { // Normalize as many pct-encoded sections as possible $string = preg_replace_callback('/(?:%[A-Fa-f0-9]{2})+/', [$this, 'remove_iunreserved_percent_encoded'], $string); \assert(\is_string($string), "For PHPStan: Should not occur, the regex is valid"); // Replace invalid percent characters $string = preg_replace('/%(?![A-Fa-f0-9]{2})/', '%25', $string); \assert(\is_string($string), "For PHPStan: Should not occur, the regex is valid"); // Add unreserved and % to $extra_chars (the latter is safe because all // pct-encoded sections are now valid). $extra_chars .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~%'; // Now replace any bytes that aren't allowed with their pct-encoded versions $position = 0; $strlen = strlen($string); while (($position += strspn($string, $extra_chars, $position)) < $strlen) { $value = ord($string[$position]); $character = 0; // Start position $start = $position; // By default we are valid $valid = true; // No one byte sequences are valid due to the while. // Two byte sequence: if (($value & 0xE0) === 0xC0) { $character = ($value & 0x1F) << 6; $length = 2; $remaining = 1; } // Three byte sequence: elseif (($value & 0xF0) === 0xE0) { $character = ($value & 0x0F) << 12; $length = 3; $remaining = 2; } // Four byte sequence: elseif (($value & 0xF8) === 0xF0) { $character = ($value & 0x07) << 18; $length = 4; $remaining = 3; } // Invalid byte: else { $valid = false; $length = 1; $remaining = 0; } if ($remaining) { if ($position + $length <= $strlen) { for ($position++; $remaining; $position++) { $value = ord($string[$position]); // Check that the byte is valid, then add it to the character: if (($value & 0xC0) === 0x80) { $character |= ($value & 0x3F) << (--$remaining * 6); } // If it is invalid, count the sequence as invalid and reprocess the current byte: else { $valid = false; $position--; break; } } } else { $position = $strlen - 1; $valid = false; } } // Percent encode anything invalid or not in ucschar if ( // Invalid sequences !$valid // Non-shortest form sequences are invalid || $length > 1 && $character <= 0x7F || $length > 2 && $character <= 0x7FF || $length > 3 && $character <= 0xFFFF // Outside of range of ucschar codepoints // Noncharacters || ($character & 0xFFFE) === 0xFFFE || $character >= 0xFDD0 && $character <= 0xFDEF || ( // Everything else not in ucschar $character > 0xD7FF && $character < 0xF900 || $character < 0xA0 || $character > 0xEFFFD ) && ( // Everything not in iprivate, if it applies !$iprivate || $character < 0xE000 || $character > 0x10FFFD ) ) { // If we were a character, pretend we weren't, but rather an error. if ($valid) { $position--; } for ($j = $start; $j <= $position; $j++) { $string = substr_replace($string, sprintf('%%%02X', ord($string[$j])), $j, 1); $j += 2; $position += 2; $strlen += 2; } } } return $string; } /** * Callback function for preg_replace_callback. * * Removes sequences of percent encoded bytes that represent UTF-8 * encoded characters in iunreserved * * @param array{string} $match PCRE match, a capture group #0 consisting of a sequence of valid percent-encoded bytes * @return string Replacement */ protected function remove_iunreserved_percent_encoded(array $match) { // As we just have valid percent encoded sequences we can just explode // and ignore the first member of the returned array (an empty string). $bytes = explode('%', $match[0]); // Initialize the new string (this is what will be returned) and that // there are no bytes remaining in the current sequence (unsurprising // at the first byte!). $string = ''; $remaining = 0; // these variables will be initialized in the loop but PHPStan is not able to detect it currently $start = 0; $character = 0; $length = 0; $valid = true; // Loop over each and every byte, and set $value to its value for ($i = 1, $len = count($bytes); $i < $len; $i++) { $value = hexdec($bytes[$i]); // If we're the first byte of sequence: if (!$remaining) { // Start position $start = $i; // By default we are valid $valid = true; // One byte sequence: if ($value <= 0x7F) { $character = $value; $length = 1; } // Two byte sequence: elseif (($value & 0xE0) === 0xC0) { $character = ($value & 0x1F) << 6; $length = 2; $remaining = 1; } // Three byte sequence: elseif (($value & 0xF0) === 0xE0) { $character = ($value & 0x0F) << 12; $length = 3; $remaining = 2; } // Four byte sequence: elseif (($value & 0xF8) === 0xF0) { $character = ($value & 0x07) << 18; $length = 4; $remaining = 3; } // Invalid byte: else { $valid = false; $remaining = 0; } } // Continuation byte: else { // Check that the byte is valid, then add it to the character: if (($value & 0xC0) === 0x80) { $remaining--; $character |= ($value & 0x3F) << ($remaining * 6); } // If it is invalid, count the sequence as invalid and reprocess the current byte as the start of a sequence: else { $valid = false; $remaining = 0; $i--; } } // If we've reached the end of the current byte sequence, append it to Unicode::$data if (!$remaining) { // Percent encode anything invalid or not in iunreserved if ( // Invalid sequences !$valid // Non-shortest form sequences are invalid || $length > 1 && $character <= 0x7F || $length > 2 && $character <= 0x7FF || $length > 3 && $character <= 0xFFFF // Outside of range of iunreserved codepoints || $character < 0x2D || $character > 0xEFFFD // Noncharacters || ($character & 0xFFFE) === 0xFFFE || $character >= 0xFDD0 && $character <= 0xFDEF // Everything else not in iunreserved (this is all BMP) || $character === 0x2F || $character > 0x39 && $character < 0x41 || $character > 0x5A && $character < 0x61 || $character > 0x7A && $character < 0x7E || $character > 0x7E && $character < 0xA0 || $character > 0xD7FF && $character < 0xF900 ) { for ($j = $start; $j <= $i; $j++) { $string .= '%' . strtoupper($bytes[$j]); } } else { for ($j = $start; $j <= $i; $j++) { // Cast for PHPStan, this will always be a number between 0 and 0xFF so hexdec will return int. $string .= chr((int) hexdec($bytes[$j])); } } } } // If we have any bytes left over they are invalid (i.e., we are // mid-way through a multi-byte sequence) if ($remaining) { for ($j = $start; $j < $len; $j++) { $string .= '%' . strtoupper($bytes[$j]); } } return $string; } /** * @return void */ protected function scheme_normalization() { if ($this->scheme === null) { return; } if (isset($this->normalization[$this->scheme]['iuserinfo']) && $this->iuserinfo === $this->normalization[$this->scheme]['iuserinfo']) { $this->iuserinfo = null; } if (isset($this->normalization[$this->scheme]['ihost']) && $this->ihost === $this->normalization[$this->scheme]['ihost']) { $this->ihost = null; } if (isset($this->normalization[$this->scheme]['port']) && $this->port === $this->normalization[$this->scheme]['port']) { $this->port = null; } if (isset($this->normalization[$this->scheme]['ipath']) && $this->ipath === $this->normalization[$this->scheme]['ipath']) { $this->ipath = ''; } if (isset($this->normalization[$this->scheme]['iquery']) && $this->iquery === $this->normalization[$this->scheme]['iquery']) { $this->iquery = null; } if (isset($this->normalization[$this->scheme]['ifragment']) && $this->ifragment === $this->normalization[$this->scheme]['ifragment']) { $this->ifragment = null; } } /** * Check if the object represents a valid IRI. This needs to be done on each * call as some things change depending on another part of the IRI. * * @return bool */ public function is_valid() { if ($this->ipath === '') { return true; } $isauthority = $this->iuserinfo !== null || $this->ihost !== null || $this->port !== null; if ($isauthority && $this->ipath[0] === '/') { return true; } if (!$isauthority && (substr($this->ipath, 0, 2) === '//')) { return false; } // Relative urls cannot have a colon in the first path segment (and the // slashes themselves are not included so skip the first character). if (!$this->scheme && !$isauthority && strpos($this->ipath, ':') !== false && strpos($this->ipath, '/', 1) !== false && strpos($this->ipath, ':') < strpos($this->ipath, '/', 1)) { return false; } return true; } /** * Set the entire IRI. Returns true on success, false on failure (if there * are any invalid characters). * * @param string|null $iri * @return bool */ public function set_iri(?string $iri, bool $clear_cache = false) { static $cache; if ($clear_cache) { $cache = null; return false; } if (!$cache) { $cache = []; } if ($iri === null) { return true; } elseif (isset($cache[$iri])) { [ $this->scheme, $this->iuserinfo, $this->ihost, $this->port, $this->ipath, $this->iquery, $this->ifragment, $return ] = $cache[$iri]; return $return; } $parsed = $this->parse_iri((string) $iri); if (!$parsed) { return false; } $return = $this->set_scheme($parsed['scheme']) && $this->set_authority($parsed['authority']) && $this->set_path($parsed['path']) && $this->set_query($parsed['query']) && $this->set_fragment($parsed['fragment']); $cache[$iri] = [ $this->scheme, $this->iuserinfo, $this->ihost, $this->port, $this->ipath, $this->iquery, $this->ifragment, $return ]; return $return; } /** * Set the scheme. Returns true on success, false on failure (if there are * any invalid characters). * * @param string|null $scheme * @return bool */ public function set_scheme(?string $scheme) { if ($scheme === null) { $this->scheme = null; } elseif (!preg_match('/^[A-Za-z][0-9A-Za-z+\-.]*$/', $scheme)) { $this->scheme = null; return false; } else { $this->scheme = strtolower($scheme); } return true; } /** * Set the authority. Returns true on success, false on failure (if there are * any invalid characters). * * @param string|null $authority * @return bool */ public function set_authority(?string $authority, bool $clear_cache = false) { static $cache; if ($clear_cache) { $cache = null; return false; } if (!$cache) { $cache = []; } if ($authority === null) { $this->iuserinfo = null; $this->ihost = null; $this->port = null; return true; } elseif (isset($cache[$authority])) { [ $this->iuserinfo, $this->ihost, $this->port, $return ] = $cache[$authority]; return $return; } $remaining = $authority; if (($iuserinfo_end = strrpos($remaining, '@')) !== false) { // Cast for PHPStan on PHP < 8.0. It does not detect that // the range is not flipped so substr cannot return false. $iuserinfo = (string) substr($remaining, 0, $iuserinfo_end); $remaining = substr($remaining, $iuserinfo_end + 1); } else { $iuserinfo = null; } if (($port_start = strpos($remaining, ':', intval(strpos($remaining, ']')))) !== false) { $port = substr($remaining, $port_start + 1); if ($port === false) { $port = null; } $remaining = substr($remaining, 0, $port_start); } else { $port = null; } $return = $this->set_userinfo($iuserinfo) && $this->set_host($remaining) && $this->set_port($port); $cache[$authority] = [ $this->iuserinfo, $this->ihost, $this->port, $return ]; return $return; } /** * Set the iuserinfo. * * @param string|null $iuserinfo * @return bool */ public function set_userinfo(?string $iuserinfo) { if ($iuserinfo === null) { $this->iuserinfo = null; } else { $this->iuserinfo = $this->replace_invalid_with_pct_encoding($iuserinfo, '!$&\'()*+,;=:'); $this->scheme_normalization(); } return true; } /** * Set the ihost. Returns true on success, false on failure (if there are * any invalid characters). * * @param string|null $ihost * @return bool */ public function set_host(?string $ihost) { if ($ihost === null) { $this->ihost = null; return true; } elseif (substr($ihost, 0, 1) === '[' && substr($ihost, -1) === ']') { if (\SimplePie\Net\IPv6::check_ipv6(substr($ihost, 1, -1))) { $this->ihost = '[' . \SimplePie\Net\IPv6::compress(substr($ihost, 1, -1)) . ']'; } else { $this->ihost = null; return false; } } else { $ihost = $this->replace_invalid_with_pct_encoding($ihost, '!$&\'()*+,;='); // Lowercase, but ignore pct-encoded sections (as they should // remain uppercase). This must be done after the previous step // as that can add unescaped characters. $position = 0; $strlen = strlen($ihost); while (($position += strcspn($ihost, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ%', $position)) < $strlen) { if ($ihost[$position] === '%') { $position += 3; } else { $ihost[$position] = strtolower($ihost[$position]); $position++; } } $this->ihost = $ihost; } $this->scheme_normalization(); return true; } /** * Set the port. Returns true on success, false on failure (if there are * any invalid characters). * * @param string|int|null $port * @return bool */ public function set_port($port) { if ($port === null) { $this->port = null; return true; } elseif (strspn((string) $port, '0123456789') === strlen((string) $port)) { $this->port = (int) $port; $this->scheme_normalization(); return true; } $this->port = null; return false; } /** * Set the ipath. * * @param string|null $ipath * @return bool */ public function set_path(?string $ipath, bool $clear_cache = false) { static $cache; if ($clear_cache) { $cache = null; return false; } if (!$cache) { $cache = []; } $ipath = (string) $ipath; if (isset($cache[$ipath])) { $this->ipath = $cache[$ipath][(int) ($this->scheme !== null)]; } else { $valid = $this->replace_invalid_with_pct_encoding($ipath, '!$&\'()*+,;=@:/'); $removed = $this->remove_dot_segments($valid); $cache[$ipath] = [$valid, $removed]; $this->ipath = ($this->scheme !== null) ? $removed : $valid; } $this->scheme_normalization(); return true; } /** * Set the iquery. * * @param string|null $iquery * @return bool */ public function set_query(?string $iquery) { if ($iquery === null) { $this->iquery = null; } else { $this->iquery = $this->replace_invalid_with_pct_encoding($iquery, '!$&\'()*+,;=:@/?', true); $this->scheme_normalization(); } return true; } /** * Set the ifragment. * * @param string|null $ifragment * @return bool */ public function set_fragment(?string $ifragment) { if ($ifragment === null) { $this->ifragment = null; } else { $this->ifragment = $this->replace_invalid_with_pct_encoding($ifragment, '!$&\'()*+,;=:@/?'); $this->scheme_normalization(); } return true; } /** * Convert an IRI to a URI (or parts thereof) * * @param string $string * @return string */ public function to_uri(string $string) { static $non_ascii; if (!$non_ascii) { $non_ascii = implode('', range("\x80", "\xFF")); } $position = 0; $strlen = strlen($string); while (($position += strcspn($string, $non_ascii, $position)) < $strlen) { $string = substr_replace($string, sprintf('%%%02X', ord($string[$position])), $position, 1); $position += 3; $strlen += 2; } return $string; } /** * Get the complete IRI * * @return string|false */ public function get_iri() { if (!$this->is_valid()) { return false; } $iri = ''; if ($this->scheme !== null) { $iri .= $this->scheme . ':'; } if (($iauthority = $this->get_iauthority()) !== null) { $iri .= '//' . $iauthority; } if ($this->ipath !== '') { $iri .= $this->ipath; } elseif (!empty($this->normalization[$this->scheme]['ipath']) && $iauthority !== null && $iauthority !== '') { $iri .= $this->normalization[$this->scheme]['ipath']; } if ($this->iquery !== null) { $iri .= '?' . $this->iquery; } if ($this->ifragment !== null) { $iri .= '#' . $this->ifragment; } return $iri; } /** * Get the complete URI * * @return string */ public function get_uri() { return $this->to_uri((string) $this->get_iri()); } /** * Get the complete iauthority * * @return ?string */ protected function get_iauthority() { if ($this->iuserinfo !== null || $this->ihost !== null || $this->port !== null) { $iauthority = ''; if ($this->iuserinfo !== null) { $iauthority .= $this->iuserinfo . '@'; } if ($this->ihost !== null) { $iauthority .= $this->ihost; } if ($this->port !== null && $this->port !== 0) { $iauthority .= ':' . $this->port; } return $iauthority; } return null; } /** * Get the complete authority * * @return ?string */ protected function get_authority() { $iauthority = $this->get_iauthority(); if (is_string($iauthority)) { return $this->to_uri($iauthority); } return $iauthority; } } class_alias('SimplePie\IRI', 'SimplePie_IRI'); ���������Net/IPv6.php����������������������������������������������������������������������������������������0000644�����������������00000016324�15250524467�0006607 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php // SPDX-FileCopyrightText: 2004-2023 Ryan Parman, Sam Sneddon, Ryan McCue // SPDX-License-Identifier: BSD-3-Clause declare(strict_types=1); namespace SimplePie\Net; /** * Class to validate and to work with IPv6 addresses. * * @copyright 2003-2005 The PHP Group * @license http://www.opensource.org/licenses/bsd-license.php * @link http://pear.php.net/package/Net_IPv6 * @author Alexander Merz <alexander.merz@web.de> * @author elfrink at introweb dot nl * @author Josh Peck <jmp at joshpeck dot org> * @author Sam Sneddon <geoffers@gmail.com> */ class IPv6 { /** * Uncompresses an IPv6 address * * RFC 4291 allows you to compress consecutive zero pieces in an address to * '::'. This method expects a valid IPv6 address and expands the '::' to * the required number of zero pieces. * * Example: FF01::101 -> FF01:0:0:0:0:0:0:101 * ::1 -> 0:0:0:0:0:0:0:1 * * @author Alexander Merz <alexander.merz@web.de> * @author elfrink at introweb dot nl * @author Josh Peck <jmp at joshpeck dot org> * @copyright 2003-2005 The PHP Group * @license http://www.opensource.org/licenses/bsd-license.php * @param string $ip An IPv6 address * @return string The uncompressed IPv6 address */ public static function uncompress(string $ip) { $c1 = -1; $c2 = -1; if (substr_count($ip, '::') === 1) { [$ip1, $ip2] = explode('::', $ip); if ($ip1 === '') { $c1 = -1; } else { $c1 = substr_count($ip1, ':'); } if ($ip2 === '') { $c2 = -1; } else { $c2 = substr_count($ip2, ':'); } if (strpos($ip2, '.') !== false) { $c2++; } // :: if ($c1 === -1 && $c2 === -1) { $ip = '0:0:0:0:0:0:0:0'; } // ::xxx elseif ($c1 === -1) { $fill = str_repeat('0:', 7 - $c2); $ip = str_replace('::', $fill, $ip); } // xxx:: elseif ($c2 === -1) { $fill = str_repeat(':0', 7 - $c1); $ip = str_replace('::', $fill, $ip); } // xxx::xxx else { $fill = ':' . str_repeat('0:', 6 - $c2 - $c1); $ip = str_replace('::', $fill, $ip); } } return $ip; } /** * Compresses an IPv6 address * * RFC 4291 allows you to compress consecutive zero pieces in an address to * '::'. This method expects a valid IPv6 address and compresses consecutive * zero pieces to '::'. * * Example: FF01:0:0:0:0:0:0:101 -> FF01::101 * 0:0:0:0:0:0:0:1 -> ::1 * * @see uncompress() * @param string $ip An IPv6 address * @return string The compressed IPv6 address */ public static function compress(string $ip) { // Prepare the IP to be compressed $ip = self::uncompress($ip); $ip_parts = self::split_v6_v4($ip); // Replace all leading zeros $ip_parts[0] = (string) preg_replace('/(^|:)0+([0-9])/', '\1\2', $ip_parts[0]); // Find bunches of zeros if (preg_match_all('/(?:^|:)(?:0(?::|$))+/', $ip_parts[0], $matches, PREG_OFFSET_CAPTURE)) { $max = 0; $pos = null; foreach ($matches[0] as $match) { if (strlen($match[0]) > $max) { $max = strlen($match[0]); $pos = $match[1]; } } assert($pos !== null, 'For PHPStan: Since the regex matched, there is at least one match. And because the pattern is non-empty, the loop will always end with $pos ≥ 1.'); $ip_parts[0] = substr_replace($ip_parts[0], '::', $pos, $max); } if ($ip_parts[1] !== '') { return implode(':', $ip_parts); } return $ip_parts[0]; } /** * Splits an IPv6 address into the IPv6 and IPv4 representation parts * * RFC 4291 allows you to represent the last two parts of an IPv6 address * using the standard IPv4 representation * * Example: 0:0:0:0:0:0:13.1.68.3 * 0:0:0:0:0:FFFF:129.144.52.38 * * @param string $ip An IPv6 address * @return array{string, string} [0] contains the IPv6 represented part, and [1] the IPv4 represented part */ private static function split_v6_v4(string $ip): array { if (strpos($ip, '.') !== false) { $pos = strrpos($ip, ':'); assert($pos !== false, 'For PHPStan: IPv6 address must contain colon, since split_v6_v4 is only ever called after uncompress.'); $ipv6_part = substr($ip, 0, $pos); $ipv4_part = substr($ip, $pos + 1); return [$ipv6_part, $ipv4_part]; } return [$ip, '']; } /** * Checks an IPv6 address * * Checks if the given IP is a valid IPv6 address * * @param string $ip An IPv6 address * @return bool true if $ip is a valid IPv6 address */ public static function check_ipv6(string $ip) { $ip = self::uncompress($ip); [$ipv6, $ipv4] = self::split_v6_v4($ip); $ipv6 = explode(':', $ipv6); $ipv4 = explode('.', $ipv4); if (count($ipv6) === 8 && count($ipv4) === 1 || count($ipv6) === 6 && count($ipv4) === 4) { foreach ($ipv6 as $ipv6_part) { // The section can't be empty if ($ipv6_part === '') { return false; } // Nor can it be over four characters if (strlen($ipv6_part) > 4) { return false; } // Remove leading zeros (this is safe because of the above) $ipv6_part = ltrim($ipv6_part, '0'); if ($ipv6_part === '') { $ipv6_part = '0'; } // Check the value is valid $value = hexdec($ipv6_part); if ($value < 0 || $value > 0xFFFF) { return false; } assert(is_int($value), 'For PHPStan: $value is only float when $ipv6_part > PHP_INT_MAX'); if (dechex($value) !== strtolower($ipv6_part)) { return false; } } if (count($ipv4) === 4) { foreach ($ipv4 as $ipv4_part) { $value = (int) $ipv4_part; if ((string) $value !== $ipv4_part || $value < 0 || $value > 0xFF) { return false; } } } return true; } return false; } /** * Checks if the given IP is a valid IPv6 address * * @codeCoverageIgnore * @deprecated Use {@see IPv6::check_ipv6()} instead * @see check_ipv6 * @param string $ip An IPv6 address * @return bool true if $ip is a valid IPv6 address */ public static function checkIPv6(string $ip) { return self::check_ipv6($ip); } } class_alias('SimplePie\Net\IPv6', 'SimplePie_Net_IPv6'); ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Source.php������������������������������������������������������������������������������������������0000644�����������������00000056272�15250524467�0006543 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php // SPDX-FileCopyrightText: 2004-2023 Ryan Parman, Sam Sneddon, Ryan McCue // SPDX-License-Identifier: BSD-3-Clause declare(strict_types=1); namespace SimplePie; /** * Handles `<atom:source>` * * Used by {@see \SimplePie\Item::get_source()} * * This class can be overloaded with {@see \SimplePie::set_source_class()} */ class Source implements RegistryAware { /** @var Item */ public $item; /** @var array<string, mixed> */ public $data = []; /** @var Registry */ protected $registry; /** * @param array<string, mixed> $data */ public function __construct(Item $item, array $data) { $this->item = $item; $this->data = $data; } /** * @return void */ public function set_registry(\SimplePie\Registry $registry) { $this->registry = $registry; } /** * @return string */ public function __toString() { return md5(serialize($this->data)); } /** * @param string $namespace * @param string $tag * @return array<array<string, mixed>>|null */ public function get_source_tags(string $namespace, string $tag) { if (isset($this->data['child'][$namespace][$tag])) { return $this->data['child'][$namespace][$tag]; } return null; } /** * @param array<string, mixed> $element * @return string */ public function get_base(array $element = []) { return $this->item->get_base($element); } /** * @param string $data * @param int-mask-of<SimplePie::CONSTRUCT_*> $type * @param string $base * @return string */ public function sanitize(string $data, $type, string $base = '') { return $this->item->sanitize($data, $type, $base); } /** * @return Item */ public function get_item() { return $this->item; } /** * @return string|null */ public function get_title() { if ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'title')) { return $this->sanitize($return[0]['data'], $this->registry->call(Misc::class, 'atom_10_construct_type', [$return[0]['attribs']]), $this->get_base($return[0])); } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_03, 'title')) { return $this->sanitize($return[0]['data'], $this->registry->call(Misc::class, 'atom_03_construct_type', [$return[0]['attribs']]), $this->get_base($return[0])); } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_RSS_10, 'title')) { return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_RSS_090, 'title')) { return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_RSS_20, 'title')) { return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_DC_11, 'title')) { return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_DC_10, 'title')) { return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } return null; } /** * @param int $key * @return Category|null */ public function get_category(int $key = 0) { $categories = $this->get_categories(); if (isset($categories[$key])) { return $categories[$key]; } return null; } /** * @return array<Category>|null */ public function get_categories() { $categories = []; foreach ((array) $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'category') as $category) { $term = null; $scheme = null; $label = null; if (isset($category['attribs']['']['term'])) { $term = $this->sanitize($category['attribs']['']['term'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($category['attribs']['']['scheme'])) { $scheme = $this->sanitize($category['attribs']['']['scheme'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($category['attribs']['']['label'])) { $label = $this->sanitize($category['attribs']['']['label'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } $categories[] = $this->registry->create(Category::class, [$term, $scheme, $label]); } foreach ((array) $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_RSS_20, 'category') as $category) { // This is really the label, but keep this as the term also for BC. // Label will also work on retrieving because that falls back to term. $term = $this->sanitize($category['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); if (isset($category['attribs']['']['domain'])) { $scheme = $this->sanitize($category['attribs']['']['domain'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } else { $scheme = null; } $categories[] = $this->registry->create(Category::class, [$term, $scheme, null]); } foreach ((array) $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_DC_11, 'subject') as $category) { $categories[] = $this->registry->create(Category::class, [$this->sanitize($category['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT), null, null]); } foreach ((array) $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_DC_10, 'subject') as $category) { $categories[] = $this->registry->create(Category::class, [$this->sanitize($category['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT), null, null]); } if (!empty($categories)) { return array_unique($categories); } return null; } /** * @param int $key * @return Author|null */ public function get_author(int $key = 0) { $authors = $this->get_authors(); if (isset($authors[$key])) { return $authors[$key]; } return null; } /** * @return array<Author>|null */ public function get_authors() { $authors = []; foreach ((array) $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'author') as $author) { $name = null; $uri = null; $email = null; if (isset($author['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['name'][0]['data'])) { $name = $this->sanitize($author['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['name'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($author['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['uri'][0]['data'])) { $uri = $author['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['uri'][0]; $uri = $this->sanitize($uri['data'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_base($uri)); } if (isset($author['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['email'][0]['data'])) { $email = $this->sanitize($author['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['email'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if ($name !== null || $email !== null || $uri !== null) { $authors[] = $this->registry->create(Author::class, [$name, $uri, $email]); } } if ($author = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_03, 'author')) { $name = null; $url = null; $email = null; if (isset($author[0]['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['name'][0]['data'])) { $name = $this->sanitize($author[0]['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['name'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($author[0]['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['url'][0]['data'])) { $url = $author[0]['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['url'][0]; $url = $this->sanitize($url['data'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_base($url)); } if (isset($author[0]['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['email'][0]['data'])) { $email = $this->sanitize($author[0]['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['email'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if ($name !== null || $email !== null || $url !== null) { $authors[] = $this->registry->create(Author::class, [$name, $url, $email]); } } foreach ((array) $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_DC_11, 'creator') as $author) { $authors[] = $this->registry->create(Author::class, [$this->sanitize($author['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT), null, null]); } foreach ((array) $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_DC_10, 'creator') as $author) { $authors[] = $this->registry->create(Author::class, [$this->sanitize($author['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT), null, null]); } foreach ((array) $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_ITUNES, 'author') as $author) { $authors[] = $this->registry->create(Author::class, [$this->sanitize($author['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT), null, null]); } if (!empty($authors)) { return array_unique($authors); } return null; } /** * @param int $key * @return Author|null */ public function get_contributor(int $key = 0) { $contributors = $this->get_contributors(); if (isset($contributors[$key])) { return $contributors[$key]; } return null; } /** * @return array<Author>|null */ public function get_contributors() { $contributors = []; foreach ((array) $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'contributor') as $contributor) { $name = null; $uri = null; $email = null; if (isset($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['name'][0]['data'])) { $name = $this->sanitize($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['name'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['uri'][0]['data'])) { $uri = $contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['uri'][0]; $uri = $this->sanitize($uri['data'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_base($uri)); } if (isset($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['email'][0]['data'])) { $email = $this->sanitize($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['email'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if ($name !== null || $email !== null || $uri !== null) { $contributors[] = $this->registry->create(Author::class, [$name, $uri, $email]); } } foreach ((array) $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_03, 'contributor') as $contributor) { $name = null; $url = null; $email = null; if (isset($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['name'][0]['data'])) { $name = $this->sanitize($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['name'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if (isset($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['url'][0]['data'])) { $url = $contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['url'][0]; $url = $this->sanitize($url['data'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_base($url)); } if (isset($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['email'][0]['data'])) { $email = $this->sanitize($contributor['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['email'][0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } if ($name !== null || $email !== null || $url !== null) { $contributors[] = $this->registry->create(Author::class, [$name, $url, $email]); } } if (!empty($contributors)) { return array_unique($contributors); } return null; } /** * @param int $key * @param string $rel * @return string|null */ public function get_link(int $key = 0, string $rel = 'alternate') { $links = $this->get_links($rel); if (isset($links[$key])) { return $links[$key]; } return null; } /** * Added for parity between the parent-level and the item/entry-level. * * @return string|null */ public function get_permalink() { return $this->get_link(0); } /** * @param string $rel * @return array<string>|null */ public function get_links(string $rel = 'alternate') { if (!isset($this->data['links'])) { $this->data['links'] = []; if ($links = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'link')) { foreach ($links as $link) { if (isset($link['attribs']['']['href'])) { $link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate'; $this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_base($link)); } } } if ($links = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_03, 'link')) { foreach ($links as $link) { if (isset($link['attribs']['']['href'])) { $link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate'; $this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_base($link)); } } } if ($links = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_RSS_10, 'link')) { $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_base($links[0])); } if ($links = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_RSS_090, 'link')) { $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_base($links[0])); } if ($links = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_RSS_20, 'link')) { $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_base($links[0])); } $keys = array_keys($this->data['links']); foreach ($keys as $key) { $key = (string) $key; if ($this->registry->call(Misc::class, 'is_isegment_nz_nc', [$key])) { if (isset($this->data['links'][\SimplePie\SimplePie::IANA_LINK_RELATIONS_REGISTRY . $key])) { $this->data['links'][\SimplePie\SimplePie::IANA_LINK_RELATIONS_REGISTRY . $key] = array_merge($this->data['links'][$key], $this->data['links'][\SimplePie\SimplePie::IANA_LINK_RELATIONS_REGISTRY . $key]); $this->data['links'][$key] = &$this->data['links'][\SimplePie\SimplePie::IANA_LINK_RELATIONS_REGISTRY . $key]; } else { $this->data['links'][\SimplePie\SimplePie::IANA_LINK_RELATIONS_REGISTRY . $key] = &$this->data['links'][$key]; } } elseif (substr($key, 0, 41) === \SimplePie\SimplePie::IANA_LINK_RELATIONS_REGISTRY) { $this->data['links'][substr($key, 41)] = &$this->data['links'][$key]; } $this->data['links'][$key] = array_unique($this->data['links'][$key]); } } if (isset($this->data['links'][$rel])) { return $this->data['links'][$rel]; } return null; } /** * @return string|null */ public function get_description() { if ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'subtitle')) { return $this->sanitize($return[0]['data'], $this->registry->call(Misc::class, 'atom_10_construct_type', [$return[0]['attribs']]), $this->get_base($return[0])); } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_03, 'tagline')) { return $this->sanitize($return[0]['data'], $this->registry->call(Misc::class, 'atom_03_construct_type', [$return[0]['attribs']]), $this->get_base($return[0])); } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_RSS_10, 'description')) { return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_RSS_090, 'description')) { return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_RSS_20, 'description')) { return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_DC_11, 'description')) { return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_DC_10, 'description')) { return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_ITUNES, 'summary')) { return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_HTML, $this->get_base($return[0])); } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_ITUNES, 'subtitle')) { return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_HTML, $this->get_base($return[0])); } return null; } /** * @return string|null */ public function get_copyright() { if ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'rights')) { return $this->sanitize($return[0]['data'], $this->registry->call(Misc::class, 'atom_10_construct_type', [$return[0]['attribs']]), $this->get_base($return[0])); } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_03, 'copyright')) { return $this->sanitize($return[0]['data'], $this->registry->call(Misc::class, 'atom_03_construct_type', [$return[0]['attribs']]), $this->get_base($return[0])); } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_RSS_20, 'copyright')) { return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_DC_11, 'rights')) { return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_DC_10, 'rights')) { return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } return null; } /** * @return string|null */ public function get_language() { if ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_RSS_20, 'language')) { return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_DC_11, 'language')) { return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_DC_10, 'language')) { return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } elseif (isset($this->data['xml_lang'])) { return $this->sanitize($this->data['xml_lang'], \SimplePie\SimplePie::CONSTRUCT_TEXT); } return null; } /** * @return float|null */ public function get_latitude() { if ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_W3C_BASIC_GEO, 'lat')) { return (float) $return[0]['data']; } elseif (($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match)) { return (float) $match[1]; } return null; } /** * @return float|null */ public function get_longitude() { if ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_W3C_BASIC_GEO, 'long')) { return (float) $return[0]['data']; } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_W3C_BASIC_GEO, 'lon')) { return (float) $return[0]['data']; } elseif (($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match)) { return (float) $match[2]; } return null; } /** * @return string|null */ public function get_image_url() { if ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_ITUNES, 'image')) { return $this->sanitize($return[0]['attribs']['']['href'], \SimplePie\SimplePie::CONSTRUCT_IRI); } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'logo')) { return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_base($return[0])); } elseif ($return = $this->get_source_tags(\SimplePie\SimplePie::NAMESPACE_ATOM_10, 'icon')) { return $this->sanitize($return[0]['data'], \SimplePie\SimplePie::CONSTRUCT_IRI, $this->get_base($return[0])); } return null; } } class_alias('SimplePie\Source', 'SimplePie_Source'); ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Registry.php����������������������������������������������������������������������������������������0000644�����������������00000017330�15250524467�0007103 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php // SPDX-FileCopyrightText: 2004-2023 Ryan Parman, Sam Sneddon, Ryan McCue // SPDX-License-Identifier: BSD-3-Clause declare(strict_types=1); namespace SimplePie; use InvalidArgumentException; use SimplePie\Content\Type\Sniffer; use SimplePie\Parse\Date; use SimplePie\XML\Declaration\Parser as DeclarationParser; /** * Handles creating objects and calling methods * * Access this via {@see \SimplePie\SimplePie::get_registry()} */ class Registry { /** * Default class mapping * * Overriding classes *must* subclass these. * * @var array<class-string, class-string> */ protected $default = [ Cache::class => Cache::class, Locator::class => Locator::class, Parser::class => Parser::class, File::class => File::class, Sanitize::class => Sanitize::class, Item::class => Item::class, Author::class => Author::class, Category::class => Category::class, Enclosure::class => Enclosure::class, Caption::class => Caption::class, Copyright::class => Copyright::class, Credit::class => Credit::class, Rating::class => Rating::class, Restriction::class => Restriction::class, Sniffer::class => Sniffer::class, Source::class => Source::class, Misc::class => Misc::class, DeclarationParser::class => DeclarationParser::class, Date::class => Date::class, ]; /** * Class mapping * * @see register() * @var array<string, class-string> */ protected $classes = []; /** * Legacy classes * * @see register() * @var array<class-string> */ protected $legacy = []; /** * Legacy types * * @see register() * @var array<string, class-string> */ private $legacyTypes = [ 'Cache' => Cache::class, 'Locator' => Locator::class, 'Parser' => Parser::class, 'File' => File::class, 'Sanitize' => Sanitize::class, 'Item' => Item::class, 'Author' => Author::class, 'Category' => Category::class, 'Enclosure' => Enclosure::class, 'Caption' => Caption::class, 'Copyright' => Copyright::class, 'Credit' => Credit::class, 'Rating' => Rating::class, 'Restriction' => Restriction::class, 'Content_Type_Sniffer' => Sniffer::class, 'Source' => Source::class, 'Misc' => Misc::class, 'XML_Declaration_Parser' => DeclarationParser::class, 'Parse_Date' => Date::class, ]; /** * Constructor * * No-op */ public function __construct() { } /** * Register a class * * @param string $type See {@see $default} for names * @param class-string $class Class name, must subclass the corresponding default * @param bool $legacy Whether to enable legacy support for this class * @return bool Successfulness */ public function register(string $type, $class, bool $legacy = false) { if (array_key_exists($type, $this->legacyTypes)) { // trigger_error(sprintf('"%s"(): Using argument #1 ($type) with value "%s" is deprecated since SimplePie 1.8.0, use class-string "%s" instead.', __METHOD__, $type, $this->legacyTypes[$type]), \E_USER_DEPRECATED); $type = $this->legacyTypes[$type]; } if (!array_key_exists($type, $this->default)) { return false; } if (!class_exists($class)) { return false; } /** @var string */ $base_class = $this->default[$type]; if (!is_subclass_of($class, $base_class)) { return false; } $this->classes[$type] = $class; if ($legacy) { $this->legacy[] = $class; } return true; } /** * Get the class registered for a type * * Where possible, use {@see create()} or {@see call()} instead * * @template T * @param class-string<T> $type * @return class-string<T>|null */ public function get_class($type) { if (array_key_exists($type, $this->legacyTypes)) { // trigger_error(sprintf('"%s"(): Using argument #1 ($type) with value "%s" is deprecated since SimplePie 1.8.0, use class-string "%s" instead.', __METHOD__, $type, $this->legacyTypes[$type]), \E_USER_DEPRECATED); $type = $this->legacyTypes[$type]; } if (!array_key_exists($type, $this->default)) { return null; } // For PHPStan: values in $default should be subtypes of keys. /** @var class-string<T> */ $class = $this->default[$type]; if (array_key_exists($type, $this->classes)) { // For PHPStan: values in $classes should be subtypes of keys. /** @var class-string<T> */ $class = $this->classes[$type]; } return $class; } /** * Create a new instance of a given type * * @template T class-string $type * @param class-string<T> $type * @param array<mixed> $parameters Parameters to pass to the constructor * @return T Instance of class */ public function &create($type, array $parameters = []) { $class = $this->get_class($type); if ($class === null) { throw new InvalidArgumentException(sprintf( '%s(): Argument #1 ($type) "%s" not found in class list.', __METHOD__, $type ), 1); } if (!method_exists($class, '__construct')) { $instance = new $class(); } else { $reflector = new \ReflectionClass($class); // For PHPStan: $class is T. /** @var T */ $instance = $reflector->newInstanceArgs($parameters); } if ($instance instanceof RegistryAware) { $instance->set_registry($this); } elseif (method_exists($instance, 'set_registry')) { trigger_error(sprintf('Using the method "set_registry()" without implementing "%s" is deprecated since SimplePie 1.8.0, implement "%s" in "%s".', RegistryAware::class, RegistryAware::class, $class), \E_USER_DEPRECATED); $instance->set_registry($this); } return $instance; } /** * Call a static method for a type * * @param class-string $type * @param string $method * @param array<mixed> $parameters * @return mixed */ public function &call($type, string $method, array $parameters = []) { $class = $this->get_class($type); if ($class === null) { throw new InvalidArgumentException(sprintf( '%s(): Argument #1 ($type) "%s" not found in class list.', __METHOD__, $type ), 1); } if (in_array($class, $this->legacy)) { switch ($type) { case Cache::class: // For backwards compatibility with old non-static // Cache::create() methods in PHP < 8.0. // No longer supported as of PHP 8.0. if ($method === 'get_handler') { // Fixing this PHPStan error breaks CacheTest::testDirectOverrideLegacy() /** @phpstan-ignore argument.type */ $result = @call_user_func_array([$class, 'create'], $parameters); return $result; } break; } } $callable = [$class, $method]; assert(is_callable($callable), 'For PHPstan'); $result = call_user_func_array($callable, $parameters); return $result; } } class_alias('SimplePie\Registry', 'SimplePie_Registry'); ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������SimplePie.php���������������������������������������������������������������������������������������0000644�����������������00000372064�15250524467�0007172 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php // SPDX-FileCopyrightText: 2004-2023 Ryan Parman, Sam Sneddon, Ryan McCue // SPDX-License-Identifier: BSD-3-Clause declare(strict_types=1); namespace SimplePie; use InvalidArgumentException; use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestFactoryInterface; use Psr\Http\Message\UriFactoryInterface; use Psr\SimpleCache\CacheInterface; use SimplePie\Cache\Base; use SimplePie\Cache\BaseDataCache; use SimplePie\Cache\CallableNameFilter; use SimplePie\Cache\DataCache; use SimplePie\Cache\NameFilter; use SimplePie\Cache\Psr16; use SimplePie\Content\Type\Sniffer; use SimplePie\Exception as SimplePieException; use SimplePie\HTTP\Client; use SimplePie\HTTP\ClientException; use SimplePie\HTTP\FileClient; use SimplePie\HTTP\Psr18Client; use SimplePie\HTTP\Response; /** * SimplePie */ class SimplePie { /** * SimplePie Name */ public const NAME = 'SimplePie'; /** * SimplePie Version */ public const VERSION = '1.9.0'; /** * SimplePie Website URL */ public const URL = 'http://simplepie.org'; /** * SimplePie Linkback */ public const LINKBACK = '<a href="' . self::URL . '" title="' . self::NAME . ' ' . self::VERSION . '">' . self::NAME . '</a>'; /** * No Autodiscovery * @see SimplePie::set_autodiscovery_level() */ public const LOCATOR_NONE = 0; /** * Feed Link Element Autodiscovery * @see SimplePie::set_autodiscovery_level() */ public const LOCATOR_AUTODISCOVERY = 1; /** * Local Feed Extension Autodiscovery * @see SimplePie::set_autodiscovery_level() */ public const LOCATOR_LOCAL_EXTENSION = 2; /** * Local Feed Body Autodiscovery * @see SimplePie::set_autodiscovery_level() */ public const LOCATOR_LOCAL_BODY = 4; /** * Remote Feed Extension Autodiscovery * @see SimplePie::set_autodiscovery_level() */ public const LOCATOR_REMOTE_EXTENSION = 8; /** * Remote Feed Body Autodiscovery * @see SimplePie::set_autodiscovery_level() */ public const LOCATOR_REMOTE_BODY = 16; /** * All Feed Autodiscovery * @see SimplePie::set_autodiscovery_level() */ public const LOCATOR_ALL = 31; /** * No known feed type */ public const TYPE_NONE = 0; /** * RSS 0.90 */ public const TYPE_RSS_090 = 1; /** * RSS 0.91 (Netscape) */ public const TYPE_RSS_091_NETSCAPE = 2; /** * RSS 0.91 (Userland) */ public const TYPE_RSS_091_USERLAND = 4; /** * RSS 0.91 (both Netscape and Userland) */ public const TYPE_RSS_091 = 6; /** * RSS 0.92 */ public const TYPE_RSS_092 = 8; /** * RSS 0.93 */ public const TYPE_RSS_093 = 16; /** * RSS 0.94 */ public const TYPE_RSS_094 = 32; /** * RSS 1.0 */ public const TYPE_RSS_10 = 64; /** * RSS 2.0 */ public const TYPE_RSS_20 = 128; /** * RDF-based RSS */ public const TYPE_RSS_RDF = 65; /** * Non-RDF-based RSS (truly intended as syndication format) */ public const TYPE_RSS_SYNDICATION = 190; /** * All RSS */ public const TYPE_RSS_ALL = 255; /** * Atom 0.3 */ public const TYPE_ATOM_03 = 256; /** * Atom 1.0 */ public const TYPE_ATOM_10 = 512; /** * All Atom */ public const TYPE_ATOM_ALL = 768; /** * All feed types */ public const TYPE_ALL = 1023; /** * No construct */ public const CONSTRUCT_NONE = 0; /** * Text construct */ public const CONSTRUCT_TEXT = 1; /** * HTML construct */ public const CONSTRUCT_HTML = 2; /** * XHTML construct */ public const CONSTRUCT_XHTML = 4; /** * base64-encoded construct */ public const CONSTRUCT_BASE64 = 8; /** * IRI construct */ public const CONSTRUCT_IRI = 16; /** * A construct that might be HTML */ public const CONSTRUCT_MAYBE_HTML = 32; /** * All constructs */ public const CONSTRUCT_ALL = 63; /** * Don't change case */ public const SAME_CASE = 1; /** * Change to lowercase */ public const LOWERCASE = 2; /** * Change to uppercase */ public const UPPERCASE = 4; /** * PCRE for HTML attributes */ public const PCRE_HTML_ATTRIBUTE = '((?:[\x09\x0A\x0B\x0C\x0D\x20]+[^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3D\x3E]*(?:[\x09\x0A\x0B\x0C\x0D\x20]*=[\x09\x0A\x0B\x0C\x0D\x20]*(?:"(?:[^"]*)"|\'(?:[^\']*)\'|(?:[^\x09\x0A\x0B\x0C\x0D\x20\x22\x27\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x3E]*)?))?)*)[\x09\x0A\x0B\x0C\x0D\x20]*'; /** * PCRE for XML attributes */ public const PCRE_XML_ATTRIBUTE = '((?:\s+(?:(?:[^\s:]+:)?[^\s:]+)\s*=\s*(?:"(?:[^"]*)"|\'(?:[^\']*)\'))*)\s*'; /** * XML Namespace */ public const NAMESPACE_XML = 'http://www.w3.org/XML/1998/namespace'; /** * Atom 1.0 Namespace */ public const NAMESPACE_ATOM_10 = 'http://www.w3.org/2005/Atom'; /** * Atom 0.3 Namespace */ public const NAMESPACE_ATOM_03 = 'http://purl.org/atom/ns#'; /** * RDF Namespace */ public const NAMESPACE_RDF = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'; /** * RSS 0.90 Namespace */ public const NAMESPACE_RSS_090 = 'http://my.netscape.com/rdf/simple/0.9/'; /** * RSS 1.0 Namespace */ public const NAMESPACE_RSS_10 = 'http://purl.org/rss/1.0/'; /** * RSS 1.0 Content Module Namespace */ public const NAMESPACE_RSS_10_MODULES_CONTENT = 'http://purl.org/rss/1.0/modules/content/'; /** * RSS 2.0 Namespace * (Stupid, I know, but I'm certain it will confuse people less with support.) */ public const NAMESPACE_RSS_20 = ''; /** * DC 1.0 Namespace */ public const NAMESPACE_DC_10 = 'http://purl.org/dc/elements/1.0/'; /** * DC 1.1 Namespace */ public const NAMESPACE_DC_11 = 'http://purl.org/dc/elements/1.1/'; /** * W3C Basic Geo (WGS84 lat/long) Vocabulary Namespace */ public const NAMESPACE_W3C_BASIC_GEO = 'http://www.w3.org/2003/01/geo/wgs84_pos#'; /** * GeoRSS Namespace */ public const NAMESPACE_GEORSS = 'http://www.georss.org/georss'; /** * Media RSS Namespace */ public const NAMESPACE_MEDIARSS = 'http://search.yahoo.com/mrss/'; /** * Wrong Media RSS Namespace. Caused by a long-standing typo in the spec. */ public const NAMESPACE_MEDIARSS_WRONG = 'http://search.yahoo.com/mrss'; /** * Wrong Media RSS Namespace #2. New namespace introduced in Media RSS 1.5. */ public const NAMESPACE_MEDIARSS_WRONG2 = 'http://video.search.yahoo.com/mrss'; /** * Wrong Media RSS Namespace #3. A possible typo of the Media RSS 1.5 namespace. */ public const NAMESPACE_MEDIARSS_WRONG3 = 'http://video.search.yahoo.com/mrss/'; /** * Wrong Media RSS Namespace #4. New spec location after the RSS Advisory Board takes it over, but not a valid namespace. */ public const NAMESPACE_MEDIARSS_WRONG4 = 'http://www.rssboard.org/media-rss'; /** * Wrong Media RSS Namespace #5. A possible typo of the RSS Advisory Board URL. */ public const NAMESPACE_MEDIARSS_WRONG5 = 'http://www.rssboard.org/media-rss/'; /** * iTunes RSS Namespace */ public const NAMESPACE_ITUNES = 'http://www.itunes.com/dtds/podcast-1.0.dtd'; /** * XHTML Namespace */ public const NAMESPACE_XHTML = 'http://www.w3.org/1999/xhtml'; /** * IANA Link Relations Registry */ public const IANA_LINK_RELATIONS_REGISTRY = 'http://www.iana.org/assignments/relation/'; /** * No file source */ public const FILE_SOURCE_NONE = 0; /** * Remote file source */ public const FILE_SOURCE_REMOTE = 1; /** * Local file source */ public const FILE_SOURCE_LOCAL = 2; /** * fsockopen() file source */ public const FILE_SOURCE_FSOCKOPEN = 4; /** * cURL file source */ public const FILE_SOURCE_CURL = 8; /** * file_get_contents() file source */ public const FILE_SOURCE_FILE_GET_CONTENTS = 16; /** * @internal Default value of the HTTP Accept header when fetching/locating feeds */ public const DEFAULT_HTTP_ACCEPT_HEADER = 'application/atom+xml, application/rss+xml, application/rdf+xml;q=0.9, application/xml;q=0.8, text/xml;q=0.8, text/html;q=0.7, unknown/unknown;q=0.1, application/unknown;q=0.1, */*;q=0.1'; /** * @var array<string, mixed> Raw data * @access private */ public $data = []; /** * @var string|string[]|null Error string (or array when multiple feeds are initialized) * @access private */ public $error = null; /** * @var int HTTP status code * @see SimplePie::status_code() * @access private */ public $status_code = 0; /** * @var Sanitize instance of Sanitize class * @see SimplePie::set_sanitize_class() * @access private */ public $sanitize; /** * @var string SimplePie Useragent * @see SimplePie::set_useragent() * @access private */ public $useragent = ''; /** * @var string Feed URL * @see SimplePie::set_feed_url() * @access private */ public $feed_url; /** * @var ?string Original feed URL, or new feed URL iff HTTP 301 Moved Permanently * @see SimplePie::subscribe_url() * @access private */ public $permanent_url = null; /** * @var File Instance of File class to use as a feed * @see SimplePie::set_file() */ private $file; /** * @var string|false Raw feed data * @see SimplePie::set_raw_data() * @access private */ public $raw_data; /** * @var int Timeout for fetching remote files * @see SimplePie::set_timeout() * @access private */ public $timeout = 10; /** * @var array<int, mixed> Custom curl options * @see SimplePie::set_curl_options() * @access private */ public $curl_options = []; /** * @var bool Forces fsockopen() to be used for remote files instead * of cURL, even if a new enough version is installed * @see SimplePie::force_fsockopen() * @access private */ public $force_fsockopen = false; /** * @var bool Force the given data/URL to be treated as a feed no matter what * it appears like * @see SimplePie::force_feed() * @access private */ public $force_feed = false; /** * @var bool Enable/Disable Caching * @see SimplePie::enable_cache() * @access private */ private $enable_cache = true; /** * @var DataCache|null * @see SimplePie::set_cache() */ private $cache = null; /** * @var NameFilter * @see SimplePie::set_cache_namefilter() */ private $cache_namefilter; /** * @var bool Force SimplePie to fallback to expired cache, if enabled, * when feed is unavailable. * @see SimplePie::force_cache_fallback() * @access private */ public $force_cache_fallback = false; /** * @var int Cache duration (in seconds) * @see SimplePie::set_cache_duration() * @access private */ public $cache_duration = 3600; /** * @var int Auto-discovery cache duration (in seconds) * @see SimplePie::set_autodiscovery_cache_duration() * @access private */ public $autodiscovery_cache_duration = 604800; // 7 Days. /** * @var string Cache location (relative to executing script) * @see SimplePie::set_cache_location() * @access private */ public $cache_location = './cache'; /** * @var string&(callable(string): string) Function that creates the cache filename * @see SimplePie::set_cache_name_function() * @access private */ public $cache_name_function = 'md5'; /** * @var bool Reorder feed by date descending * @see SimplePie::enable_order_by_date() * @access private */ public $order_by_date = true; /** * @var mixed Force input encoding to be set to the follow value * (false, or anything type-cast to false, disables this feature) * @see SimplePie::set_input_encoding() * @access private */ public $input_encoding = false; /** * @var self::LOCATOR_* Feed Autodiscovery Level * @see SimplePie::set_autodiscovery_level() * @access private */ public $autodiscovery = self::LOCATOR_ALL; /** * Class registry object * * @var Registry */ public $registry; /** * @var int Maximum number of feeds to check with autodiscovery * @see SimplePie::set_max_checked_feeds() * @access private */ public $max_checked_feeds = 10; /** * @var array<Response>|null All the feeds found during the autodiscovery process * @see SimplePie::get_all_discovered_feeds() * @access private */ public $all_discovered_feeds = []; /** * @var string Web-accessible path to the handler_image.php file. * @see SimplePie::set_image_handler() * @access private */ public $image_handler = ''; /** * @var array<string> Stores the URLs when multiple feeds are being initialized. * @see SimplePie::set_feed_url() * @access private */ public $multifeed_url = []; /** * @var array<int, static> Stores SimplePie objects when multiple feeds initialized. * @access private */ public $multifeed_objects = []; /** * @var array<mixed> Stores the get_object_vars() array for use with multifeeds. * @see SimplePie::set_feed_url() * @access private */ public $config_settings = null; /** * @var int Stores the number of items to return per-feed with multifeeds. * @see SimplePie::set_item_limit() * @access private */ public $item_limit = 0; /** * @var bool Stores if last-modified and/or etag headers were sent with the * request when checking a feed. */ public $check_modified = false; /** * @var array<string> Stores the default attributes to be stripped by strip_attributes(). * @see SimplePie::strip_attributes() * @access private */ public $strip_attributes = ['bgsound', 'class', 'expr', 'id', 'style', 'onclick', 'onerror', 'onfinish', 'onmouseover', 'onmouseout', 'onfocus', 'onblur', 'lowsrc', 'dynsrc']; /** * @var array<string, array<string, string>> Stores the default attributes to add to different tags by add_attributes(). * @see SimplePie::add_attributes() * @access private */ public $add_attributes = ['audio' => ['preload' => 'none'], 'iframe' => ['sandbox' => 'allow-scripts allow-same-origin'], 'video' => ['preload' => 'none']]; /** * @var array<string> Stores the default tags to be stripped by strip_htmltags(). * @see SimplePie::strip_htmltags() * @access private */ public $strip_htmltags = ['base', 'blink', 'body', 'doctype', 'embed', 'font', 'form', 'frame', 'frameset', 'html', 'iframe', 'input', 'marquee', 'meta', 'noscript', 'object', 'param', 'script', 'style']; /** * @var string[]|string Stores the default attributes to be renamed by rename_attributes(). * @see SimplePie::rename_attributes() * @access private */ public $rename_attributes = []; /** * @var bool Should we throw exceptions, or use the old-style error property? * @access private */ public $enable_exceptions = false; /** * @var Client|null */ private $http_client = null; /** @var bool Whether HTTP client has been injected */ private $http_client_injected = false; /** * The SimplePie class contains feed level data and options * * To use SimplePie, create the SimplePie object with no parameters. You can * then set configuration options using the provided methods. After setting * them, you must initialise the feed using $feed->init(). At that point the * object's methods and properties will be available to you. * * Previously, it was possible to pass in the feed URL along with cache * options directly into the constructor. This has been removed as of 1.3 as * it caused a lot of confusion. * * @since 1.0 Preview Release */ public function __construct() { if (version_compare(PHP_VERSION, '7.2', '<')) { exit('Please upgrade to PHP 7.2 or newer.'); } $this->set_useragent(); $this->set_cache_namefilter(new CallableNameFilter($this->cache_name_function)); // Other objects, instances created here so we can set options on them $this->sanitize = new Sanitize(); $this->registry = new Registry(); if (func_num_args() > 0) { trigger_error('Passing parameters to the constructor is no longer supported. Please use set_feed_url(), set_cache_location(), and set_cache_duration() directly.', \E_USER_DEPRECATED); $args = func_get_args(); switch (count($args)) { case 3: $this->set_cache_duration($args[2]); // no break case 2: $this->set_cache_location($args[1]); // no break case 1: $this->set_feed_url($args[0]); $this->init(); } } } /** * Used for converting object to a string * @return string */ public function __toString() { return md5(serialize($this->data)); } /** * Remove items that link back to this before destroying this object * @return void */ public function __destruct() { if (!gc_enabled()) { if (!empty($this->data['items'])) { foreach ($this->data['items'] as $item) { $item->__destruct(); } unset($item, $this->data['items']); } if (!empty($this->data['ordered_items'])) { foreach ($this->data['ordered_items'] as $item) { $item->__destruct(); } unset($item, $this->data['ordered_items']); } } } /** * Force the given data/URL to be treated as a feed * * This tells SimplePie to ignore the content-type provided by the server. * Be careful when using this option, as it will also disable autodiscovery. * * @since 1.1 * @param bool $enable Force the given data/URL to be treated as a feed * @return void */ public function force_feed(bool $enable = false) { $this->force_feed = $enable; } /** * Set the URL of the feed you want to parse * * This allows you to enter the URL of the feed you want to parse, or the * website you want to try to use auto-discovery on. This takes priority * over any set raw data. * * Deprecated since 1.9.0: You can set multiple feeds to mash together by passing an array instead * of a string for the $url. Remember that with each additional feed comes * additional processing and resources. * * @since 1.0 Preview Release * @see set_raw_data() * @param string|string[] $url This is the URL (or (deprecated) array of URLs) that you want to parse. * @return void */ public function set_feed_url($url) { $this->multifeed_url = []; if (is_array($url)) { trigger_error('Fetching multiple feeds with single SimplePie instance is deprecated since SimplePie 1.9.0, create one SimplePie instance per feed and use SimplePie::merge_items to get a single list of items.', \E_USER_DEPRECATED); foreach ($url as $value) { $this->multifeed_url[] = $this->registry->call(Misc::class, 'fix_protocol', [$value, 1]); } } else { $this->feed_url = $this->registry->call(Misc::class, 'fix_protocol', [$url, 1]); $this->permanent_url = $this->feed_url; } } /** * Set an instance of {@see File} to use as a feed * * @deprecated since SimplePie 1.9.0, use \SimplePie\SimplePie::set_http_client() or \SimplePie\SimplePie::set_raw_data() instead. * * @param File &$file * @return bool True on success, false on failure */ public function set_file(File &$file) { // trigger_error(sprintf('SimplePie\SimplePie::set_file() is deprecated since SimplePie 1.9.0, please use "SimplePie\SimplePie::set_http_client()" or "SimplePie\SimplePie::set_raw_data()" instead.'), \E_USER_DEPRECATED); $this->feed_url = $file->get_final_requested_uri(); $this->permanent_url = $this->feed_url; $this->file = &$file; return true; } /** * Set the raw XML data to parse * * Allows you to use a string of RSS/Atom data instead of a remote feed. * * If you have a feed available as a string in PHP, you can tell SimplePie * to parse that data string instead of a remote feed. Any set feed URL * takes precedence. * * @since 1.0 Beta 3 * @param string $data RSS or Atom data as a string. * @see set_feed_url() * @return void */ public function set_raw_data(string $data) { $this->raw_data = $data; } /** * Set a PSR-18 client and PSR-17 factories * * Allows you to use your own HTTP client implementations. * This will become required with SimplePie 2.0.0. */ final public function set_http_client( ClientInterface $http_client, RequestFactoryInterface $request_factory, UriFactoryInterface $uri_factory ): void { $this->http_client = new Psr18Client($http_client, $request_factory, $uri_factory); } /** * Set the default timeout for fetching remote feeds * * This allows you to change the maximum time the feed's server to respond * and send the feed back. * * @since 1.0 Beta 3 * @param int $timeout The maximum number of seconds to spend waiting to retrieve a feed. * @return void */ public function set_timeout(int $timeout = 10) { if ($this->http_client_injected) { throw new SimplePieException(sprintf( 'Using "%s()" has no effect, because you already provided a HTTP client with "%s::set_http_client()". Configure timeout in your HTTP client instead.', __METHOD__, self::class )); } $this->timeout = (int) $timeout; // Reset a possible existing FileClient, // so a new client with the changed value will be created if (is_object($this->http_client) && $this->http_client instanceof FileClient) { $this->http_client = null; } elseif (is_object($this->http_client)) { // Trigger notice if a PSR-18 client was set trigger_error(sprintf( 'Using "%s()" has no effect, because you already provided a HTTP client with "%s::set_http_client()". Configure the timeout in your HTTP client instead.', __METHOD__, get_class($this) ), \E_USER_NOTICE); } } /** * Set custom curl options * * This allows you to change default curl options * * @since 1.0 Beta 3 * @param array<int, mixed> $curl_options Curl options to add to default settings * @return void */ public function set_curl_options(array $curl_options = []) { if ($this->http_client_injected) { throw new SimplePieException(sprintf( 'Using "%s()" has no effect, because you already provided a HTTP client with "%s::set_http_client()". Configure custom curl options in your HTTP client instead.', __METHOD__, self::class )); } $this->curl_options = $curl_options; // Reset a possible existing FileClient, // so a new client with the changed value will be created if (is_object($this->http_client) && $this->http_client instanceof FileClient) { $this->http_client = null; } elseif (is_object($this->http_client)) { // Trigger notice if a PSR-18 client was set trigger_error(sprintf( 'Using "%s()" has no effect, because you already provided a HTTP client with "%s::set_http_client()". Configure the curl options in your HTTP client instead.', __METHOD__, get_class($this) ), \E_USER_NOTICE); } } /** * Force SimplePie to use fsockopen() instead of cURL * * @since 1.0 Beta 3 * @param bool $enable Force fsockopen() to be used * @return void */ public function force_fsockopen(bool $enable = false) { if ($this->http_client_injected) { throw new SimplePieException(sprintf( 'Using "%s()" has no effect, because you already provided a HTTP client with "%s::set_http_client()". Configure fsockopen in your HTTP client instead.', __METHOD__, self::class )); } $this->force_fsockopen = $enable; // Reset a possible existing FileClient, // so a new client with the changed value will be created if (is_object($this->http_client) && $this->http_client instanceof FileClient) { $this->http_client = null; } elseif (is_object($this->http_client)) { // Trigger notice if a PSR-18 client was set trigger_error(sprintf( 'Using "%s()" has no effect, because you already provided a HTTP client with "%s::set_http_client()". Configure fsockopen in your HTTP client instead.', __METHOD__, get_class($this) ), \E_USER_NOTICE); } } /** * Enable/disable caching in SimplePie. * * This option allows you to disable caching all-together in SimplePie. * However, disabling the cache can lead to longer load times. * * @since 1.0 Preview Release * @param bool $enable Enable caching * @return void */ public function enable_cache(bool $enable = true) { $this->enable_cache = $enable; } /** * Set a PSR-16 implementation as cache * * @param CacheInterface $cache The PSR-16 cache implementation * * @return void */ public function set_cache(CacheInterface $cache) { $this->cache = new Psr16($cache); } /** * SimplePie to continue to fall back to expired cache, if enabled, when * feed is unavailable. * * This tells SimplePie to ignore any file errors and fall back to cache * instead. This only works if caching is enabled and cached content * still exists. * * @deprecated since SimplePie 1.8.0, expired cache will not be used anymore. * * @param bool $enable Force use of cache on fail. * @return void */ public function force_cache_fallback(bool $enable = false) { // @trigger_error(sprintf('SimplePie\SimplePie::force_cache_fallback() is deprecated since SimplePie 1.8.0, expired cache will not be used anymore.'), \E_USER_DEPRECATED); $this->force_cache_fallback = $enable; } /** * Set the length of time (in seconds) that the contents of a feed will be * cached * * @param int $seconds The feed content cache duration * @return void */ public function set_cache_duration(int $seconds = 3600) { $this->cache_duration = $seconds; } /** * Set the length of time (in seconds) that the autodiscovered feed URL will * be cached * * @param int $seconds The autodiscovered feed URL cache duration. * @return void */ public function set_autodiscovery_cache_duration(int $seconds = 604800) { $this->autodiscovery_cache_duration = $seconds; } /** * Set the file system location where the cached files should be stored * * @deprecated since SimplePie 1.8.0, use SimplePie::set_cache() instead. * * @param string $location The file system location. * @return void */ public function set_cache_location(string $location = './cache') { // @trigger_error(sprintf('SimplePie\SimplePie::set_cache_location() is deprecated since SimplePie 1.8.0, please use "SimplePie\SimplePie::set_cache()" instead.'), \E_USER_DEPRECATED); $this->cache_location = $location; } /** * Return the filename (i.e. hash, without path and without extension) of the file to cache a given URL. * * @param string $url The URL of the feed to be cached. * @return string A filename (i.e. hash, without path and without extension). */ public function get_cache_filename(string $url) { // Append custom parameters to the URL to avoid cache pollution in case of multiple calls with different parameters. $url .= $this->force_feed ? '#force_feed' : ''; $options = []; if ($this->timeout != 10) { $options[CURLOPT_TIMEOUT] = $this->timeout; } if ($this->useragent !== Misc::get_default_useragent()) { $options[CURLOPT_USERAGENT] = $this->useragent; } if (!empty($this->curl_options)) { foreach ($this->curl_options as $k => $v) { $options[$k] = $v; } } if (!empty($options)) { ksort($options); $url .= '#' . urlencode(var_export($options, true)); } return $this->cache_namefilter->filter($url); } /** * Set whether feed items should be sorted into reverse chronological order * * @param bool $enable Sort as reverse chronological order. * @return void */ public function enable_order_by_date(bool $enable = true) { $this->order_by_date = $enable; } /** * Set the character encoding used to parse the feed * * This overrides the encoding reported by the feed, however it will fall * back to the normal encoding detection if the override fails * * @param string|false $encoding Character encoding * @return void */ public function set_input_encoding($encoding = false) { if ($encoding) { $this->input_encoding = (string) $encoding; } else { $this->input_encoding = false; } } /** * Set how much feed autodiscovery to do * * @see self::LOCATOR_NONE * @see self::LOCATOR_AUTODISCOVERY * @see self::LOCATOR_LOCAL_EXTENSION * @see self::LOCATOR_LOCAL_BODY * @see self::LOCATOR_REMOTE_EXTENSION * @see self::LOCATOR_REMOTE_BODY * @see self::LOCATOR_ALL * @param self::LOCATOR_* $level Feed Autodiscovery Level (level can be a combination of the above constants, see bitwise OR operator) * @return void */ public function set_autodiscovery_level(int $level = self::LOCATOR_ALL) { $this->autodiscovery = $level; } /** * Get the class registry * * Use this to override SimplePie's default classes * * @return Registry */ public function &get_registry() { return $this->registry; } /** * Set which class SimplePie uses for caching * * @deprecated since SimplePie 1.3, use {@see set_cache()} instead * * @param class-string<Cache> $class Name of custom class * * @return bool True on success, false otherwise */ public function set_cache_class(string $class = Cache::class) { trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::set_cache()" instead.', __METHOD__), \E_USER_DEPRECATED); return $this->registry->register(Cache::class, $class, true); } /** * Set which class SimplePie uses for auto-discovery * * @deprecated since SimplePie 1.3, use {@see get_registry()} instead * * @param class-string<Locator> $class Name of custom class * * @return bool True on success, false otherwise */ public function set_locator_class(string $class = Locator::class) { trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED); return $this->registry->register(Locator::class, $class, true); } /** * Set which class SimplePie uses for XML parsing * * @deprecated since SimplePie 1.3, use {@see get_registry()} instead * * @param class-string<Parser> $class Name of custom class * * @return bool True on success, false otherwise */ public function set_parser_class(string $class = Parser::class) { trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED); return $this->registry->register(Parser::class, $class, true); } /** * Set which class SimplePie uses for remote file fetching * * @deprecated since SimplePie 1.3, use {@see get_registry()} instead * * @param class-string<File> $class Name of custom class * * @return bool True on success, false otherwise */ public function set_file_class(string $class = File::class) { trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED); return $this->registry->register(File::class, $class, true); } /** * Set which class SimplePie uses for data sanitization * * @deprecated since SimplePie 1.3, use {@see get_registry()} instead * * @param class-string<Sanitize> $class Name of custom class * * @return bool True on success, false otherwise */ public function set_sanitize_class(string $class = Sanitize::class) { trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED); return $this->registry->register(Sanitize::class, $class, true); } /** * Set which class SimplePie uses for handling feed items * * @deprecated since SimplePie 1.3, use {@see get_registry()} instead * * @param class-string<Item> $class Name of custom class * * @return bool True on success, false otherwise */ public function set_item_class(string $class = Item::class) { trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED); return $this->registry->register(Item::class, $class, true); } /** * Set which class SimplePie uses for handling author data * * @deprecated since SimplePie 1.3, use {@see get_registry()} instead * * @param class-string<Author> $class Name of custom class * * @return bool True on success, false otherwise */ public function set_author_class(string $class = Author::class) { trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED); return $this->registry->register(Author::class, $class, true); } /** * Set which class SimplePie uses for handling category data * * @deprecated since SimplePie 1.3, use {@see get_registry()} instead * * @param class-string<Category> $class Name of custom class * * @return bool True on success, false otherwise */ public function set_category_class(string $class = Category::class) { trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED); return $this->registry->register(Category::class, $class, true); } /** * Set which class SimplePie uses for feed enclosures * * @deprecated since SimplePie 1.3, use {@see get_registry()} instead * * @param class-string<Enclosure> $class Name of custom class * * @return bool True on success, false otherwise */ public function set_enclosure_class(string $class = Enclosure::class) { trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED); return $this->registry->register(Enclosure::class, $class, true); } /** * Set which class SimplePie uses for `<media:text>` captions * * @deprecated since SimplePie 1.3, use {@see get_registry()} instead * * @param class-string<Caption> $class Name of custom class * * @return bool True on success, false otherwise */ public function set_caption_class(string $class = Caption::class) { trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED); return $this->registry->register(Caption::class, $class, true); } /** * Set which class SimplePie uses for `<media:copyright>` * * @deprecated since SimplePie 1.3, use {@see get_registry()} instead * * @param class-string<Copyright> $class Name of custom class * * @return bool True on success, false otherwise */ public function set_copyright_class(string $class = Copyright::class) { trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED); return $this->registry->register(Copyright::class, $class, true); } /** * Set which class SimplePie uses for `<media:credit>` * * @deprecated since SimplePie 1.3, use {@see get_registry()} instead * * @param class-string<Credit> $class Name of custom class * * @return bool True on success, false otherwise */ public function set_credit_class(string $class = Credit::class) { trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED); return $this->registry->register(Credit::class, $class, true); } /** * Set which class SimplePie uses for `<media:rating>` * * @deprecated since SimplePie 1.3, use {@see get_registry()} instead * * @param class-string<Rating> $class Name of custom class * * @return bool True on success, false otherwise */ public function set_rating_class(string $class = Rating::class) { trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED); return $this->registry->register(Rating::class, $class, true); } /** * Set which class SimplePie uses for `<media:restriction>` * * @deprecated since SimplePie 1.3, use {@see get_registry()} instead * * @param class-string<Restriction> $class Name of custom class * * @return bool True on success, false otherwise */ public function set_restriction_class(string $class = Restriction::class) { trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED); return $this->registry->register(Restriction::class, $class, true); } /** * Set which class SimplePie uses for content-type sniffing * * @deprecated since SimplePie 1.3, use {@see get_registry()} instead * * @param class-string<Sniffer> $class Name of custom class * * @return bool True on success, false otherwise */ public function set_content_type_sniffer_class(string $class = Sniffer::class) { trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED); return $this->registry->register(Sniffer::class, $class, true); } /** * Set which class SimplePie uses item sources * * @deprecated since SimplePie 1.3, use {@see get_registry()} instead * * @param class-string<Source> $class Name of custom class * * @return bool True on success, false otherwise */ public function set_source_class(string $class = Source::class) { trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED); return $this->registry->register(Source::class, $class, true); } /** * Set the user agent string * * @param string $ua New user agent string. * @return void */ public function set_useragent(?string $ua = null) { if ($this->http_client_injected) { throw new SimplePieException(sprintf( 'Using "%s()" has no effect, because you already provided a HTTP client with "%s::set_http_client()". Configure user agent string in your HTTP client instead.', __METHOD__, self::class )); } if ($ua === null) { $ua = Misc::get_default_useragent(); } $this->useragent = (string) $ua; // Reset a possible existing FileClient, // so a new client with the changed value will be created if (is_object($this->http_client) && $this->http_client instanceof FileClient) { $this->http_client = null; } elseif (is_object($this->http_client)) { // Trigger notice if a PSR-18 client was set trigger_error(sprintf( 'Using "%s()" has no effect, because you already provided a HTTP client with "%s::set_http_client()". Configure the useragent in your HTTP client instead.', __METHOD__, get_class($this) ), \E_USER_NOTICE); } } /** * Set a namefilter to modify the cache filename with * * @param NameFilter $filter * * @return void */ public function set_cache_namefilter(NameFilter $filter): void { $this->cache_namefilter = $filter; } /** * Set callback function to create cache filename with * * @deprecated since SimplePie 1.8.0, use {@see set_cache_namefilter()} instead * * @param (string&(callable(string): string))|null $function Callback function * @return void */ public function set_cache_name_function(?string $function = null) { // trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.8.0, please use "SimplePie\SimplePie::set_cache_namefilter()" instead.', __METHOD__), \E_USER_DEPRECATED); if ($function === null) { $function = 'md5'; } $this->cache_name_function = $function; $this->set_cache_namefilter(new CallableNameFilter($this->cache_name_function)); } /** * Set options to make SP as fast as possible * * Forgoes a substantial amount of data sanitization in favor of speed. This * turns SimplePie into a dumb parser of feeds. * * @param bool $set Whether to set them or not * @return void */ public function set_stupidly_fast(bool $set = false) { if ($set) { $this->enable_order_by_date(false); $this->remove_div(false); $this->strip_comments(false); $this->strip_htmltags([]); $this->strip_attributes([]); $this->add_attributes([]); $this->set_image_handler(false); $this->set_https_domains([]); } } /** * Set maximum number of feeds to check with autodiscovery * * @param int $max Maximum number of feeds to check * @return void */ public function set_max_checked_feeds(int $max = 10) { $this->max_checked_feeds = $max; } /** * @return void */ public function remove_div(bool $enable = true) { $this->sanitize->remove_div($enable); } /** * @param string[]|string|false $tags Set a list of tags to strip, or set empty string to use default tags, or false to strip nothing. * @return void */ public function strip_htmltags($tags = '', ?bool $encode = null) { if ($tags === '') { $tags = $this->strip_htmltags; } $this->sanitize->strip_htmltags($tags); if ($encode !== null) { $this->sanitize->encode_instead_of_strip($encode); } } /** * @return void */ public function encode_instead_of_strip(bool $enable = true) { $this->sanitize->encode_instead_of_strip($enable); } /** * @param string[]|string $attribs * @return void */ public function rename_attributes($attribs = '') { if ($attribs === '') { $attribs = $this->rename_attributes; } $this->sanitize->rename_attributes($attribs); } /** * @param string[]|string $attribs * @return void */ public function strip_attributes($attribs = '') { if ($attribs === '') { $attribs = $this->strip_attributes; } $this->sanitize->strip_attributes($attribs); } /** * @param array<string, array<string, string>>|'' $attribs * @return void */ public function add_attributes($attribs = '') { if ($attribs === '') { $attribs = $this->add_attributes; } $this->sanitize->add_attributes($attribs); } /** * Set the output encoding * * Allows you to override SimplePie's output to match that of your webpage. * This is useful for times when your webpages are not being served as * UTF-8. This setting will be obeyed by {@see handle_content_type()}, and * is similar to {@see set_input_encoding()}. * * It should be noted, however, that not all character encodings can support * all characters. If your page is being served as ISO-8859-1 and you try * to display a Japanese feed, you'll likely see garbled characters. * Because of this, it is highly recommended to ensure that your webpages * are served as UTF-8. * * The number of supported character encodings depends on whether your web * host supports {@link http://php.net/mbstring mbstring}, * {@link http://php.net/iconv iconv}, or both. See * {@link http://simplepie.org/wiki/faq/Supported_Character_Encodings} for * more information. * * @param string $encoding * @return void */ public function set_output_encoding(string $encoding = 'UTF-8') { $this->sanitize->set_output_encoding($encoding); } /** * @return void */ public function strip_comments(bool $strip = false) { $this->sanitize->strip_comments($strip); } /** * Set element/attribute key/value pairs of HTML attributes * containing URLs that need to be resolved relative to the feed * * Defaults to |a|@href, |area|@href, |blockquote|@cite, |del|@cite, * |form|@action, |img|@longdesc, |img|@src, |input|@src, |ins|@cite, * |q|@cite * * @since 1.0 * @param array<string, string|string[]>|null $element_attribute Element/attribute key/value pairs, null for default * @return void */ public function set_url_replacements(?array $element_attribute = null) { $this->sanitize->set_url_replacements($element_attribute); } /** * Set the list of domains for which to force HTTPS. * @see Sanitize::set_https_domains() * @param array<string> $domains List of HTTPS domains. Example array('biz', 'example.com', 'example.org', 'www.example.net'). * @return void */ public function set_https_domains(array $domains = []) { $this->sanitize->set_https_domains($domains); } /** * Set the handler to enable the display of cached images. * * @param string|false $page Web-accessible path to the handler_image.php file. * @param string $qs The query string that the value should be passed to. * @return void */ public function set_image_handler($page = false, string $qs = 'i') { if ($page !== false) { $this->sanitize->set_image_handler($page . '?' . $qs . '='); } else { $this->image_handler = ''; } } /** * Set the limit for items returned per-feed with multifeeds * * @param int $limit The maximum number of items to return. * @return void */ public function set_item_limit(int $limit = 0) { $this->item_limit = $limit; } /** * Enable throwing exceptions * * @param bool $enable Should we throw exceptions, or use the old-style error property? * @return void */ public function enable_exceptions(bool $enable = true) { $this->enable_exceptions = $enable; } /** * Initialize the feed object * * This is what makes everything happen. Period. This is where all of the * configuration options get processed, feeds are fetched, cached, and * parsed, and all of that other good stuff. * * @return bool True if successful, false otherwise */ public function init() { // Check absolute bare minimum requirements. if (!extension_loaded('xml') || !extension_loaded('pcre')) { $this->error = 'XML or PCRE extensions not loaded!'; return false; } // Then check the xml extension is sane (i.e., libxml 2.7.x issue on PHP < 5.2.9 and libxml 2.7.0 to 2.7.2 on any version) if we don't have xmlreader. elseif (!extension_loaded('xmlreader')) { static $xml_is_sane = null; if ($xml_is_sane === null) { $parser_check = xml_parser_create(); xml_parse_into_struct($parser_check, '<foo>&</foo>', $values); if (\PHP_VERSION_ID < 80000) { xml_parser_free($parser_check); } $xml_is_sane = isset($values[0]['value']); } if (!$xml_is_sane) { return false; } } // The default sanitize class gets set in the constructor, check if it has // changed. if ($this->registry->get_class(Sanitize::class) !== Sanitize::class) { $this->sanitize = $this->registry->create(Sanitize::class); } if (method_exists($this->sanitize, 'set_registry')) { $this->sanitize->set_registry($this->registry); } // Pass whatever was set with config options over to the sanitizer. // Pass the classes in for legacy support; new classes should use the registry instead $cache = $this->registry->get_class(Cache::class); \assert($cache !== null, 'Cache must be defined'); $this->sanitize->pass_cache_data( $this->enable_cache, $this->cache_location, $this->cache_namefilter, $cache, $this->cache ); $http_client = $this->get_http_client(); if ($http_client instanceof Psr18Client) { $this->sanitize->set_http_client( $http_client->getHttpClient(), $http_client->getRequestFactory(), $http_client->getUriFactory() ); } if (!empty($this->multifeed_url)) { $i = 0; $success = 0; $this->multifeed_objects = []; $this->error = []; foreach ($this->multifeed_url as $url) { $this->multifeed_objects[$i] = clone $this; $this->multifeed_objects[$i]->set_feed_url($url); $single_success = $this->multifeed_objects[$i]->init(); $success |= $single_success; if (!$single_success) { $this->error[$i] = $this->multifeed_objects[$i]->error(); } $i++; } return (bool) $success; } elseif ($this->feed_url === null && $this->raw_data === null) { return false; } $this->error = null; $this->data = []; $this->check_modified = false; $this->multifeed_objects = []; $cache = false; if ($this->feed_url !== null) { $parsed_feed_url = $this->registry->call(Misc::class, 'parse_url', [$this->feed_url]); // Decide whether to enable caching if ($this->enable_cache && $parsed_feed_url['scheme'] !== '') { $cache = $this->get_cache($this->feed_url); } // Fetch the data into $this->raw_data if (($fetched = $this->fetch_data($cache)) === true) { return true; } elseif ($fetched === false) { return false; } [$headers, $sniffed] = $fetched; } // Empty response check if (empty($this->raw_data)) { $this->error = "A feed could not be found at `$this->feed_url`. Empty body."; $this->registry->call(Misc::class, 'error', [$this->error, E_USER_NOTICE, __FILE__, __LINE__]); return false; } // Set up array of possible encodings $encodings = []; // First check to see if input has been overridden. if ($this->input_encoding !== false) { $encodings[] = strtoupper($this->input_encoding); } $application_types = ['application/xml', 'application/xml-dtd', 'application/xml-external-parsed-entity']; $text_types = ['text/xml', 'text/xml-external-parsed-entity']; // RFC 3023 (only applies to sniffed content) if (isset($sniffed)) { if (in_array($sniffed, $application_types) || substr($sniffed, 0, 12) === 'application/' && substr($sniffed, -4) === '+xml') { if (isset($headers['content-type']) && preg_match('/;\x20?charset=([^;]*)/i', $headers['content-type'], $charset)) { $encodings[] = strtoupper($charset[1]); } $encodings = array_merge($encodings, $this->registry->call(Misc::class, 'xml_encoding', [$this->raw_data, &$this->registry])); $encodings[] = 'UTF-8'; } elseif (in_array($sniffed, $text_types) || substr($sniffed, 0, 5) === 'text/' && substr($sniffed, -4) === '+xml') { if (isset($headers['content-type']) && preg_match('/;\x20?charset=([^;]*)/i', $headers['content-type'], $charset)) { $encodings[] = strtoupper($charset[1]); } $encodings[] = 'US-ASCII'; } // Text MIME-type default elseif (substr($sniffed, 0, 5) === 'text/') { $encodings[] = 'UTF-8'; } } // Fallback to XML 1.0 Appendix F.1/UTF-8/ISO-8859-1 $encodings = array_merge($encodings, $this->registry->call(Misc::class, 'xml_encoding', [$this->raw_data, &$this->registry])); $encodings[] = 'UTF-8'; $encodings[] = 'ISO-8859-1'; // There's no point in trying an encoding twice $encodings = array_unique($encodings); // Loop through each possible encoding, till we return something, or run out of possibilities foreach ($encodings as $encoding) { // Change the encoding to UTF-8 (as we always use UTF-8 internally) if ($utf8_data = $this->registry->call(Misc::class, 'change_encoding', [$this->raw_data, $encoding, 'UTF-8'])) { // Create new parser $parser = $this->registry->create(Parser::class); // If it's parsed fine if ($parser->parse($utf8_data, 'UTF-8', $this->permanent_url ?? '')) { $this->data = $parser->get_data(); if (!($this->get_type() & ~self::TYPE_NONE)) { $this->error = "A feed could not be found at `$this->feed_url`. This does not appear to be a valid RSS or Atom feed."; $this->registry->call(Misc::class, 'error', [$this->error, E_USER_NOTICE, __FILE__, __LINE__]); return false; } if (isset($headers)) { $this->data['headers'] = $headers; } $this->data['build'] = Misc::get_build(); // Cache the file if caching is enabled $this->data['cache_expiration_time'] = $this->cache_duration + time(); if ($cache && !$cache->set_data($this->get_cache_filename($this->feed_url), $this->data, $this->cache_duration)) { trigger_error("$this->cache_location is not writable. Make sure you've set the correct relative or absolute path, and that the location is server-writable.", E_USER_WARNING); } return true; } } } if (isset($parser)) { // We have an error, just set Misc::error to it and quit $this->error = $this->feed_url; $this->error .= sprintf(' is invalid XML, likely due to invalid characters. XML error: %s at line %d, column %d', $parser->get_error_string(), $parser->get_current_line(), $parser->get_current_column()); } else { $this->error = 'The data could not be converted to UTF-8.'; if (!extension_loaded('mbstring') && !extension_loaded('iconv') && !class_exists('\UConverter')) { $this->error .= ' You MUST have either the iconv, mbstring or intl (PHP 5.5+) extension installed and enabled.'; } else { $missingExtensions = []; if (!extension_loaded('iconv')) { $missingExtensions[] = 'iconv'; } if (!extension_loaded('mbstring')) { $missingExtensions[] = 'mbstring'; } if (!class_exists('\UConverter')) { $missingExtensions[] = 'intl (PHP 5.5+)'; } $this->error .= ' Try installing/enabling the ' . implode(' or ', $missingExtensions) . ' extension.'; } } $this->registry->call(Misc::class, 'error', [$this->error, E_USER_NOTICE, __FILE__, __LINE__]); return false; } /** * Fetch the data * * If the data is already cached, attempt to fetch it from there instead * * @param Base|DataCache|false $cache Cache handler, or false to not load from the cache * @return array{array<string, string>, string}|bool Returns true if the data was loaded from the cache, or an array of HTTP headers and sniffed type */ protected function fetch_data(&$cache) { if ($cache instanceof Base) { // @trigger_error(sprintf('Providing $cache as "\SimplePie\Cache\Base" in %s() is deprecated since SimplePie 1.8.0, please provide "\SimplePie\Cache\DataCache" implementation instead.', __METHOD__), \E_USER_DEPRECATED); $cache = new BaseDataCache($cache); } // @phpstan-ignore-next-line Enforce PHPDoc type. if ($cache !== false && !$cache instanceof DataCache) { throw new InvalidArgumentException(sprintf( '%s(): Argument #1 ($cache) must be of type %s|false', __METHOD__, DataCache::class ), 1); } $cacheKey = $this->get_cache_filename($this->feed_url); // If it's enabled, use the cache if ($cache) { // Load the Cache $this->data = $cache->get_data($cacheKey, []); if (!empty($this->data)) { // If the cache is for an outdated build of SimplePie if (!isset($this->data['build']) || $this->data['build'] !== Misc::get_build()) { $cache->delete_data($cacheKey); $this->data = []; } // If we've hit a collision just rerun it with caching disabled elseif (isset($this->data['url']) && $this->data['url'] !== $this->feed_url) { $cache = false; $this->data = []; } // If we've got a non feed_url stored (if the page isn't actually a feed, or is a redirect) use that URL. elseif (isset($this->data['feed_url'])) { // Do not need to do feed autodiscovery yet. if ($this->data['feed_url'] !== $this->data['url']) { $this->set_feed_url($this->data['feed_url']); $this->data['url'] = $this->data['feed_url']; $cache->set_data($this->get_cache_filename($this->feed_url), $this->data, $this->autodiscovery_cache_duration); return $this->init(); } $cache->delete_data($this->get_cache_filename($this->feed_url)); $this->data = []; } // Check if the cache has been updated elseif (!isset($this->data['cache_expiration_time']) || $this->data['cache_expiration_time'] < time()) { // Want to know if we tried to send last-modified and/or etag headers // when requesting this file. (Note that it's up to the file to // support this, but we don't always send the headers either.) $this->check_modified = true; if (isset($this->data['headers']['last-modified']) || isset($this->data['headers']['etag'])) { $headers = [ 'Accept' => SimplePie::DEFAULT_HTTP_ACCEPT_HEADER, ]; if (isset($this->data['headers']['last-modified'])) { $headers['if-modified-since'] = $this->data['headers']['last-modified']; } if (isset($this->data['headers']['etag'])) { $headers['if-none-match'] = $this->data['headers']['etag']; } try { $file = $this->get_http_client()->request(Client::METHOD_GET, $this->feed_url, $headers); $this->status_code = $file->get_status_code(); } catch (ClientException $th) { $this->check_modified = false; $this->status_code = 0; if ($this->force_cache_fallback) { $this->data['cache_expiration_time'] = $this->cache_duration + time(); $cache->set_data($cacheKey, $this->data, $this->cache_duration); return true; } $failedFileReason = $th->getMessage(); } if ($this->status_code === 304) { // Set raw_data to false here too, to signify that the cache // is still valid. $this->raw_data = false; $this->data['cache_expiration_time'] = $this->cache_duration + time(); $cache->set_data($cacheKey, $this->data, $this->cache_duration); return true; } } } // If the cache is still valid, just return true else { $this->raw_data = false; return true; } } // If the cache is empty else { $this->data = []; } } // If we don't already have the file (it'll only exist if we've opened it to check if the cache has been modified), open it. if (!isset($file)) { if ($this->file instanceof File && $this->file->get_final_requested_uri() === $this->feed_url) { $file = &$this->file; } elseif (isset($failedFileReason)) { // Do not try to fetch again if we already failed once. // If the file connection had an error, set SimplePie::error to that and quit $this->error = $failedFileReason; return !empty($this->data); } else { $headers = [ 'Accept' => SimplePie::DEFAULT_HTTP_ACCEPT_HEADER, ]; try { $file = $this->get_http_client()->request(Client::METHOD_GET, $this->feed_url, $headers); } catch (ClientException $th) { // If the file connection has an error, set SimplePie::error to that and quit $this->error = $th->getMessage(); return !empty($this->data); } } } $this->status_code = $file->get_status_code(); // If the file connection has an error, set SimplePie::error to that and quit if (!(!Misc::is_remote_uri($file->get_final_requested_uri()) || ($file->get_status_code() === 200 || $file->get_status_code() > 206 && $file->get_status_code() < 300))) { $this->error = 'Retrieved unsupported status code "' . $this->status_code . '"'; return !empty($this->data); } if (!$this->force_feed) { // Check if the supplied URL is a feed, if it isn't, look for it. $locate = $this->registry->create(Locator::class, [ (!$file instanceof File) ? File::fromResponse($file) : $file, $this->timeout, $this->useragent, $this->max_checked_feeds, $this->force_fsockopen, $this->curl_options ]); $http_client = $this->get_http_client(); if ($http_client instanceof Psr18Client) { $locate->set_http_client( $http_client->getHttpClient(), $http_client->getRequestFactory(), $http_client->getUriFactory() ); } if (!$locate->is_feed($file)) { $copyStatusCode = $file->get_status_code(); $copyContentType = $file->get_header_line('content-type'); try { $microformats = false; if (class_exists('DOMXpath') && function_exists('Mf2\parse')) { $doc = new \DOMDocument(); @$doc->loadHTML($file->get_body_content()); $xpath = new \DOMXpath($doc); // Check for both h-feed and h-entry, as both a feed with no entries // and a list of entries without an h-feed wrapper are both valid. $query = '//*[contains(concat(" ", @class, " "), " h-feed ") or '. 'contains(concat(" ", @class, " "), " h-entry ")]'; /** @var \DOMNodeList<\DOMElement> $result */ $result = $xpath->query($query); $microformats = $result->length !== 0; } // Now also do feed discovery, but if microformats were found don't // overwrite the current value of file. $discovered = $locate->find( $this->autodiscovery, $this->all_discovered_feeds ); if ($microformats) { $hub = $locate->get_rel_link('hub'); $self = $locate->get_rel_link('self'); if ($hub || $self) { $file = $this->store_links($file, $hub, $self); } // Push the current file onto all_discovered feeds so the user can // be shown this as one of the options. if ($this->all_discovered_feeds !== null) { $this->all_discovered_feeds[] = $file; } } else { if ($discovered) { $file = $discovered; } else { // We need to unset this so that if SimplePie::set_file() has // been called that object is untouched unset($file); $this->error = "A feed could not be found at `$this->feed_url`; the status code is `$copyStatusCode` and content-type is `$copyContentType`"; $this->registry->call(Misc::class, 'error', [$this->error, E_USER_NOTICE, __FILE__, __LINE__]); return false; } } } catch (SimplePieException $e) { // We need to unset this so that if SimplePie::set_file() has been called that object is untouched unset($file); // This is usually because DOMDocument doesn't exist $this->error = $e->getMessage(); $this->registry->call(Misc::class, 'error', [$this->error, E_USER_NOTICE, $e->getFile(), $e->getLine()]); return false; } if ($cache) { $this->data = [ 'url' => $this->feed_url, 'feed_url' => $file->get_final_requested_uri(), 'build' => Misc::get_build(), 'cache_expiration_time' => $this->cache_duration + time(), ]; if (!$cache->set_data($cacheKey, $this->data, $this->cache_duration)) { trigger_error("$this->cache_location is not writable. Make sure you've set the correct relative or absolute path, and that the location is server-writable.", E_USER_WARNING); } } } $this->feed_url = $file->get_final_requested_uri(); $locate = null; } $this->raw_data = $file->get_body_content(); $this->permanent_url = $file->get_permanent_uri(); $headers = []; foreach ($file->get_headers() as $key => $values) { $headers[$key] = implode(', ', $values); } $sniffer = $this->registry->create(Sniffer::class, [&$file]); $sniffed = $sniffer->get_type(); return [$headers, $sniffed]; } /** * Get the error message for the occurred error * * @return string|string[]|null Error message, or array of messages for multifeeds */ public function error() { return $this->error; } /** * Get the last HTTP status code * * @return int Status code */ public function status_code() { return $this->status_code; } /** * Get the raw XML * * This is the same as the old `$feed->enable_xml_dump(true)`, but returns * the data instead of printing it. * * @return string|false Raw XML data, false if the cache is used */ public function get_raw_data() { return $this->raw_data; } /** * Get the character encoding used for output * * @since Preview Release * @return string */ public function get_encoding() { return $this->sanitize->output_encoding; } /** * Send the content-type header with correct encoding * * This method ensures that the SimplePie-enabled page is being served with * the correct {@link http://www.iana.org/assignments/media-types/ mime-type} * and character encoding HTTP headers (character encoding determined by the * {@see set_output_encoding} config option). * * This won't work properly if any content or whitespace has already been * sent to the browser, because it relies on PHP's * {@link http://php.net/header header()} function, and these are the * circumstances under which the function works. * * Because it's setting these settings for the entire page (as is the nature * of HTTP headers), this should only be used once per page (again, at the * top). * * @param string $mime MIME type to serve the page as * @return void */ public function handle_content_type(string $mime = 'text/html') { if (!headers_sent()) { $header = "Content-type: $mime;"; if ($this->get_encoding()) { $header .= ' charset=' . $this->get_encoding(); } else { $header .= ' charset=UTF-8'; } header($header); } } /** * Get the type of the feed * * This returns a self::TYPE_* constant, which can be tested against * using {@link http://php.net/language.operators.bitwise bitwise operators} * * @since 0.8 (usage changed to using constants in 1.0) * @see self::TYPE_NONE Unknown. * @see self::TYPE_RSS_090 RSS 0.90. * @see self::TYPE_RSS_091_NETSCAPE RSS 0.91 (Netscape). * @see self::TYPE_RSS_091_USERLAND RSS 0.91 (Userland). * @see self::TYPE_RSS_091 RSS 0.91. * @see self::TYPE_RSS_092 RSS 0.92. * @see self::TYPE_RSS_093 RSS 0.93. * @see self::TYPE_RSS_094 RSS 0.94. * @see self::TYPE_RSS_10 RSS 1.0. * @see self::TYPE_RSS_20 RSS 2.0.x. * @see self::TYPE_RSS_RDF RDF-based RSS. * @see self::TYPE_RSS_SYNDICATION Non-RDF-based RSS (truly intended as syndication format). * @see self::TYPE_RSS_ALL Any version of RSS. * @see self::TYPE_ATOM_03 Atom 0.3. * @see self::TYPE_ATOM_10 Atom 1.0. * @see self::TYPE_ATOM_ALL Any version of Atom. * @see self::TYPE_ALL Any known/supported feed type. * @return int-mask-of<self::TYPE_*> constant */ public function get_type() { if (!isset($this->data['type'])) { $this->data['type'] = self::TYPE_ALL; if (isset($this->data['child'][self::NAMESPACE_ATOM_10]['feed'])) { $this->data['type'] &= self::TYPE_ATOM_10; } elseif (isset($this->data['child'][self::NAMESPACE_ATOM_03]['feed'])) { $this->data['type'] &= self::TYPE_ATOM_03; } elseif (isset($this->data['child'][self::NAMESPACE_RDF]['RDF'])) { if (isset($this->data['child'][self::NAMESPACE_RDF]['RDF'][0]['child'][self::NAMESPACE_RSS_10]['channel']) || isset($this->data['child'][self::NAMESPACE_RDF]['RDF'][0]['child'][self::NAMESPACE_RSS_10]['image']) || isset($this->data['child'][self::NAMESPACE_RDF]['RDF'][0]['child'][self::NAMESPACE_RSS_10]['item']) || isset($this->data['child'][self::NAMESPACE_RDF]['RDF'][0]['child'][self::NAMESPACE_RSS_10]['textinput'])) { $this->data['type'] &= self::TYPE_RSS_10; } if (isset($this->data['child'][self::NAMESPACE_RDF]['RDF'][0]['child'][self::NAMESPACE_RSS_090]['channel']) || isset($this->data['child'][self::NAMESPACE_RDF]['RDF'][0]['child'][self::NAMESPACE_RSS_090]['image']) || isset($this->data['child'][self::NAMESPACE_RDF]['RDF'][0]['child'][self::NAMESPACE_RSS_090]['item']) || isset($this->data['child'][self::NAMESPACE_RDF]['RDF'][0]['child'][self::NAMESPACE_RSS_090]['textinput'])) { $this->data['type'] &= self::TYPE_RSS_090; } } elseif (isset($this->data['child'][self::NAMESPACE_RSS_20]['rss'])) { $this->data['type'] &= self::TYPE_RSS_ALL; if (isset($this->data['child'][self::NAMESPACE_RSS_20]['rss'][0]['attribs']['']['version'])) { switch (trim($this->data['child'][self::NAMESPACE_RSS_20]['rss'][0]['attribs']['']['version'])) { case '0.91': $this->data['type'] &= self::TYPE_RSS_091; if (isset($this->data['child'][self::NAMESPACE_RSS_20]['rss'][0]['child'][self::NAMESPACE_RSS_20]['skiphours']['hour'][0]['data'])) { switch (trim($this->data['child'][self::NAMESPACE_RSS_20]['rss'][0]['child'][self::NAMESPACE_RSS_20]['skiphours']['hour'][0]['data'])) { case '0': $this->data['type'] &= self::TYPE_RSS_091_NETSCAPE; break; case '24': $this->data['type'] &= self::TYPE_RSS_091_USERLAND; break; } } break; case '0.92': $this->data['type'] &= self::TYPE_RSS_092; break; case '0.93': $this->data['type'] &= self::TYPE_RSS_093; break; case '0.94': $this->data['type'] &= self::TYPE_RSS_094; break; case '2.0': $this->data['type'] &= self::TYPE_RSS_20; break; } } } else { $this->data['type'] = self::TYPE_NONE; } } return $this->data['type']; } /** * Get the URL for the feed * * When the 'permanent' mode is enabled, returns the original feed URL, * except in the case of an `HTTP 301 Moved Permanently` status response, * in which case the location of the first redirection is returned. * * When the 'permanent' mode is disabled (default), * may or may not be different from the URL passed to {@see set_feed_url()}, * depending on whether auto-discovery was used, and whether there were * any redirects along the way. * * @since Preview Release (previously called `get_feed_url()` since SimplePie 0.8.) * @todo Support <itunes:new-feed-url> * @todo Also, |atom:link|@rel=self * @param bool $permanent Permanent mode to return only the original URL or the first redirection * iff it is a 301 redirection * @return string|null */ public function subscribe_url(bool $permanent = false) { if ($permanent) { if ($this->permanent_url !== null) { // sanitize encodes ampersands which are required when used in a url. return str_replace( '&', '&', $this->sanitize( $this->permanent_url, self::CONSTRUCT_IRI ) ); } } else { if ($this->feed_url !== null) { return str_replace( '&', '&', $this->sanitize( $this->feed_url, self::CONSTRUCT_IRI ) ); } } return null; } /** * Get data for an feed-level element * * This method allows you to get access to ANY element/attribute that is a * sub-element of the opening feed tag. * * The return value is an indexed array of elements matching the given * namespace and tag name. Each element has `attribs`, `data` and `child` * subkeys. For `attribs` and `child`, these contain namespace subkeys. * `attribs` then has one level of associative name => value data (where * `value` is a string) after the namespace. `child` has tag-indexed keys * after the namespace, each member of which is an indexed array matching * this same format. * * For example: * <pre> * // This is probably a bad example because we already support * // <media:content> natively, but it shows you how to parse through * // the nodes. * $group = $item->get_item_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'group'); * $content = $group[0]['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['content']; * $file = $content[0]['attribs']['']['url']; * echo $file; * </pre> * * @since 1.0 * @see http://simplepie.org/wiki/faq/supported_xml_namespaces * @param string $namespace The URL of the XML namespace of the elements you're trying to access * @param string $tag Tag name * @return array<array<string, mixed>>|null */ public function get_feed_tags(string $namespace, string $tag) { $type = $this->get_type(); if ($type & self::TYPE_ATOM_10) { if (isset($this->data['child'][self::NAMESPACE_ATOM_10]['feed'][0]['child'][$namespace][$tag])) { return $this->data['child'][self::NAMESPACE_ATOM_10]['feed'][0]['child'][$namespace][$tag]; } } if ($type & self::TYPE_ATOM_03) { if (isset($this->data['child'][self::NAMESPACE_ATOM_03]['feed'][0]['child'][$namespace][$tag])) { return $this->data['child'][self::NAMESPACE_ATOM_03]['feed'][0]['child'][$namespace][$tag]; } } if ($type & self::TYPE_RSS_RDF) { if (isset($this->data['child'][self::NAMESPACE_RDF]['RDF'][0]['child'][$namespace][$tag])) { return $this->data['child'][self::NAMESPACE_RDF]['RDF'][0]['child'][$namespace][$tag]; } } if ($type & self::TYPE_RSS_SYNDICATION) { if (isset($this->data['child'][self::NAMESPACE_RSS_20]['rss'][0]['child'][$namespace][$tag])) { return $this->data['child'][self::NAMESPACE_RSS_20]['rss'][0]['child'][$namespace][$tag]; } } return null; } /** * Get data for an channel-level element * * This method allows you to get access to ANY element/attribute in the * channel/header section of the feed. * * See {@see SimplePie::get_feed_tags()} for a description of the return value * * @since 1.0 * @see http://simplepie.org/wiki/faq/supported_xml_namespaces * @param string $namespace The URL of the XML namespace of the elements you're trying to access * @param string $tag Tag name * @return array<array<string, mixed>>|null */ public function get_channel_tags(string $namespace, string $tag) { $type = $this->get_type(); if ($type & self::TYPE_ATOM_ALL) { if ($return = $this->get_feed_tags($namespace, $tag)) { return $return; } } if ($type & self::TYPE_RSS_10) { if ($channel = $this->get_feed_tags(self::NAMESPACE_RSS_10, 'channel')) { if (isset($channel[0]['child'][$namespace][$tag])) { return $channel[0]['child'][$namespace][$tag]; } } } if ($type & self::TYPE_RSS_090) { if ($channel = $this->get_feed_tags(self::NAMESPACE_RSS_090, 'channel')) { if (isset($channel[0]['child'][$namespace][$tag])) { return $channel[0]['child'][$namespace][$tag]; } } } if ($type & self::TYPE_RSS_SYNDICATION) { if ($channel = $this->get_feed_tags(self::NAMESPACE_RSS_20, 'channel')) { if (isset($channel[0]['child'][$namespace][$tag])) { return $channel[0]['child'][$namespace][$tag]; } } } return null; } /** * Get data for an channel-level element * * This method allows you to get access to ANY element/attribute in the * image/logo section of the feed. * * See {@see SimplePie::get_feed_tags()} for a description of the return value * * @since 1.0 * @see http://simplepie.org/wiki/faq/supported_xml_namespaces * @param string $namespace The URL of the XML namespace of the elements you're trying to access * @param string $tag Tag name * @return array<array<string, mixed>>|null */ public function get_image_tags(string $namespace, string $tag) { $type = $this->get_type(); if ($type & self::TYPE_RSS_10) { if ($image = $this->get_feed_tags(self::NAMESPACE_RSS_10, 'image')) { if (isset($image[0]['child'][$namespace][$tag])) { return $image[0]['child'][$namespace][$tag]; } } } if ($type & self::TYPE_RSS_090) { if ($image = $this->get_feed_tags(self::NAMESPACE_RSS_090, 'image')) { if (isset($image[0]['child'][$namespace][$tag])) { return $image[0]['child'][$namespace][$tag]; } } } if ($type & self::TYPE_RSS_SYNDICATION) { if ($image = $this->get_channel_tags(self::NAMESPACE_RSS_20, 'image')) { if (isset($image[0]['child'][$namespace][$tag])) { return $image[0]['child'][$namespace][$tag]; } } } return null; } /** * Get the base URL value from the feed * * Uses `<xml:base>` if available, * otherwise uses the first 'self' link or the first 'alternate' link of the feed, * or failing that, the URL of the feed itself. * * @see get_link * @see subscribe_url * * @param array<string, mixed> $element * @return string */ public function get_base(array $element = []) { if (!empty($element['xml_base_explicit']) && isset($element['xml_base'])) { return $element['xml_base']; } if (($link = $this->get_link(0, 'alternate')) !== null) { return $link; } if (($link = $this->get_link(0, 'self')) !== null) { return $link; } return $this->subscribe_url() ?? ''; } /** * Sanitize feed data * * @access private * @see Sanitize::sanitize() * @param string $data Data to sanitize * @param int-mask-of<SimplePie::CONSTRUCT_*> $type * @param string $base Base URL to resolve URLs against * @return string Sanitized data */ public function sanitize(string $data, int $type, string $base = '') { try { // This really returns string|false but changing encoding is uncommon and we are going to deprecate it, so let’s just lie to PHPStan in the interest of cleaner annotations. return $this->sanitize->sanitize($data, $type, $base); } catch (SimplePieException $e) { if (!$this->enable_exceptions) { $this->error = $e->getMessage(); $this->registry->call(Misc::class, 'error', [$this->error, E_USER_WARNING, $e->getFile(), $e->getLine()]); return ''; } throw $e; } } /** * Get the title of the feed * * Uses `<atom:title>`, `<title>` or `<dc:title>` * * @since 1.0 (previously called `get_feed_title` since 0.8) * @return string|null */ public function get_title() { if ($return = $this->get_channel_tags(self::NAMESPACE_ATOM_10, 'title')) { return $this->sanitize($return[0]['data'], $this->registry->call(Misc::class, 'atom_10_construct_type', [$return[0]['attribs']]), $this->get_base($return[0])); } elseif ($return = $this->get_channel_tags(self::NAMESPACE_ATOM_03, 'title')) { return $this->sanitize($return[0]['data'], $this->registry->call(Misc::class, 'atom_03_construct_type', [$return[0]['attribs']]), $this->get_base($return[0])); } elseif ($return = $this->get_channel_tags(self::NAMESPACE_RSS_10, 'title')) { return $this->sanitize($return[0]['data'], self::CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); } elseif ($return = $this->get_channel_tags(self::NAMESPACE_RSS_090, 'title')) { return $this->sanitize($return[0]['data'], self::CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); } elseif ($return = $this->get_channel_tags(self::NAMESPACE_RSS_20, 'title')) { return $this->sanitize($return[0]['data'], self::CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); } elseif ($return = $this->get_channel_tags(self::NAMESPACE_DC_11, 'title')) { return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT); } elseif ($return = $this->get_channel_tags(self::NAMESPACE_DC_10, 'title')) { return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT); } return null; } /** * Get a category for the feed * * @since Unknown * @param int $key The category that you want to return. Remember that arrays begin with 0, not 1 * @return Category|null */ public function get_category(int $key = 0) { $categories = $this->get_categories(); if (isset($categories[$key])) { return $categories[$key]; } return null; } /** * Get all categories for the feed * * Uses `<atom:category>`, `<category>` or `<dc:subject>` * * @since Unknown * @return array<Category>|null List of {@see Category} objects */ public function get_categories() { $categories = []; foreach ((array) $this->get_channel_tags(self::NAMESPACE_ATOM_10, 'category') as $category) { $term = null; $scheme = null; $label = null; if (isset($category['attribs']['']['term'])) { $term = $this->sanitize($category['attribs']['']['term'], self::CONSTRUCT_TEXT); } if (isset($category['attribs']['']['scheme'])) { $scheme = $this->sanitize($category['attribs']['']['scheme'], self::CONSTRUCT_TEXT); } if (isset($category['attribs']['']['label'])) { $label = $this->sanitize($category['attribs']['']['label'], self::CONSTRUCT_TEXT); } $categories[] = $this->registry->create(Category::class, [$term, $scheme, $label]); } foreach ((array) $this->get_channel_tags(self::NAMESPACE_RSS_20, 'category') as $category) { // This is really the label, but keep this as the term also for BC. // Label will also work on retrieving because that falls back to term. $term = $this->sanitize($category['data'], self::CONSTRUCT_TEXT); if (isset($category['attribs']['']['domain'])) { $scheme = $this->sanitize($category['attribs']['']['domain'], self::CONSTRUCT_TEXT); } else { $scheme = null; } $categories[] = $this->registry->create(Category::class, [$term, $scheme, null]); } foreach ((array) $this->get_channel_tags(self::NAMESPACE_DC_11, 'subject') as $category) { $categories[] = $this->registry->create(Category::class, [$this->sanitize($category['data'], self::CONSTRUCT_TEXT), null, null]); } foreach ((array) $this->get_channel_tags(self::NAMESPACE_DC_10, 'subject') as $category) { $categories[] = $this->registry->create(Category::class, [$this->sanitize($category['data'], self::CONSTRUCT_TEXT), null, null]); } if (!empty($categories)) { return array_unique($categories); } return null; } /** * Get an author for the feed * * @since 1.1 * @param int $key The author that you want to return. Remember that arrays begin with 0, not 1 * @return Author|null */ public function get_author(int $key = 0) { $authors = $this->get_authors(); if (isset($authors[$key])) { return $authors[$key]; } return null; } /** * Get all authors for the feed * * Uses `<atom:author>`, `<author>`, `<dc:creator>` or `<itunes:author>` * * @since 1.1 * @return array<Author>|null List of {@see Author} objects */ public function get_authors() { $authors = []; foreach ((array) $this->get_channel_tags(self::NAMESPACE_ATOM_10, 'author') as $author) { $name = null; $uri = null; $email = null; if (isset($author['child'][self::NAMESPACE_ATOM_10]['name'][0]['data'])) { $name = $this->sanitize($author['child'][self::NAMESPACE_ATOM_10]['name'][0]['data'], self::CONSTRUCT_TEXT); } if (isset($author['child'][self::NAMESPACE_ATOM_10]['uri'][0]['data'])) { $uri = $author['child'][self::NAMESPACE_ATOM_10]['uri'][0]; $uri = $this->sanitize($uri['data'], self::CONSTRUCT_IRI, $this->get_base($uri)); } if (isset($author['child'][self::NAMESPACE_ATOM_10]['email'][0]['data'])) { $email = $this->sanitize($author['child'][self::NAMESPACE_ATOM_10]['email'][0]['data'], self::CONSTRUCT_TEXT); } if ($name !== null || $email !== null || $uri !== null) { $authors[] = $this->registry->create(Author::class, [$name, $uri, $email]); } } if ($author = $this->get_channel_tags(self::NAMESPACE_ATOM_03, 'author')) { $name = null; $url = null; $email = null; if (isset($author[0]['child'][self::NAMESPACE_ATOM_03]['name'][0]['data'])) { $name = $this->sanitize($author[0]['child'][self::NAMESPACE_ATOM_03]['name'][0]['data'], self::CONSTRUCT_TEXT); } if (isset($author[0]['child'][self::NAMESPACE_ATOM_03]['url'][0]['data'])) { $url = $author[0]['child'][self::NAMESPACE_ATOM_03]['url'][0]; $url = $this->sanitize($url['data'], self::CONSTRUCT_IRI, $this->get_base($url)); } if (isset($author[0]['child'][self::NAMESPACE_ATOM_03]['email'][0]['data'])) { $email = $this->sanitize($author[0]['child'][self::NAMESPACE_ATOM_03]['email'][0]['data'], self::CONSTRUCT_TEXT); } if ($name !== null || $email !== null || $url !== null) { $authors[] = $this->registry->create(Author::class, [$name, $url, $email]); } } foreach ((array) $this->get_channel_tags(self::NAMESPACE_DC_11, 'creator') as $author) { $authors[] = $this->registry->create(Author::class, [$this->sanitize($author['data'], self::CONSTRUCT_TEXT), null, null]); } foreach ((array) $this->get_channel_tags(self::NAMESPACE_DC_10, 'creator') as $author) { $authors[] = $this->registry->create(Author::class, [$this->sanitize($author['data'], self::CONSTRUCT_TEXT), null, null]); } foreach ((array) $this->get_channel_tags(self::NAMESPACE_ITUNES, 'author') as $author) { $authors[] = $this->registry->create(Author::class, [$this->sanitize($author['data'], self::CONSTRUCT_TEXT), null, null]); } if (!empty($authors)) { return array_unique($authors); } return null; } /** * Get a contributor for the feed * * @since 1.1 * @param int $key The contrbutor that you want to return. Remember that arrays begin with 0, not 1 * @return Author|null */ public function get_contributor(int $key = 0) { $contributors = $this->get_contributors(); if (isset($contributors[$key])) { return $contributors[$key]; } return null; } /** * Get all contributors for the feed * * Uses `<atom:contributor>` * * @since 1.1 * @return array<Author>|null List of {@see Author} objects */ public function get_contributors() { $contributors = []; foreach ((array) $this->get_channel_tags(self::NAMESPACE_ATOM_10, 'contributor') as $contributor) { $name = null; $uri = null; $email = null; if (isset($contributor['child'][self::NAMESPACE_ATOM_10]['name'][0]['data'])) { $name = $this->sanitize($contributor['child'][self::NAMESPACE_ATOM_10]['name'][0]['data'], self::CONSTRUCT_TEXT); } if (isset($contributor['child'][self::NAMESPACE_ATOM_10]['uri'][0]['data'])) { $uri = $contributor['child'][self::NAMESPACE_ATOM_10]['uri'][0]; $uri = $this->sanitize($uri['data'], self::CONSTRUCT_IRI, $this->get_base($uri)); } if (isset($contributor['child'][self::NAMESPACE_ATOM_10]['email'][0]['data'])) { $email = $this->sanitize($contributor['child'][self::NAMESPACE_ATOM_10]['email'][0]['data'], self::CONSTRUCT_TEXT); } if ($name !== null || $email !== null || $uri !== null) { $contributors[] = $this->registry->create(Author::class, [$name, $uri, $email]); } } foreach ((array) $this->get_channel_tags(self::NAMESPACE_ATOM_03, 'contributor') as $contributor) { $name = null; $url = null; $email = null; if (isset($contributor['child'][self::NAMESPACE_ATOM_03]['name'][0]['data'])) { $name = $this->sanitize($contributor['child'][self::NAMESPACE_ATOM_03]['name'][0]['data'], self::CONSTRUCT_TEXT); } if (isset($contributor['child'][self::NAMESPACE_ATOM_03]['url'][0]['data'])) { $url = $contributor['child'][self::NAMESPACE_ATOM_03]['url'][0]; $url = $this->sanitize($url['data'], self::CONSTRUCT_IRI, $this->get_base($url)); } if (isset($contributor['child'][self::NAMESPACE_ATOM_03]['email'][0]['data'])) { $email = $this->sanitize($contributor['child'][self::NAMESPACE_ATOM_03]['email'][0]['data'], self::CONSTRUCT_TEXT); } if ($name !== null || $email !== null || $url !== null) { $contributors[] = $this->registry->create(Author::class, [$name, $url, $email]); } } if (!empty($contributors)) { return array_unique($contributors); } return null; } /** * Get a single link for the feed * * @since 1.0 (previously called `get_feed_link` since Preview Release, `get_feed_permalink()` since 0.8) * @param int $key The link that you want to return. Remember that arrays begin with 0, not 1 * @param string $rel The relationship of the link to return * @return string|null Link URL */ public function get_link(int $key = 0, string $rel = 'alternate') { $links = $this->get_links($rel); if (isset($links[$key])) { return $links[$key]; } return null; } /** * Get the permalink for the item * * Returns the first link available with a relationship of "alternate". * Identical to {@see get_link()} with key 0 * * @see get_link * @since 1.0 (previously called `get_feed_link` since Preview Release, `get_feed_permalink()` since 0.8) * @internal Added for parity between the parent-level and the item/entry-level. * @return string|null Link URL */ public function get_permalink() { return $this->get_link(0); } /** * Get all links for the feed * * Uses `<atom:link>` or `<link>` * * @since Beta 2 * @param string $rel The relationship of links to return * @return array<string>|null Links found for the feed (strings) */ public function get_links(string $rel = 'alternate') { if (!isset($this->data['links'])) { $this->data['links'] = []; if ($links = $this->get_channel_tags(self::NAMESPACE_ATOM_10, 'link')) { foreach ($links as $link) { if (isset($link['attribs']['']['href'])) { $link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate'; $this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], self::CONSTRUCT_IRI, $this->get_base($link)); } } } if ($links = $this->get_channel_tags(self::NAMESPACE_ATOM_03, 'link')) { foreach ($links as $link) { if (isset($link['attribs']['']['href'])) { $link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate'; $this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], self::CONSTRUCT_IRI, $this->get_base($link)); } } } if ($links = $this->get_channel_tags(self::NAMESPACE_RSS_10, 'link')) { $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], self::CONSTRUCT_IRI, $this->get_base($links[0])); } if ($links = $this->get_channel_tags(self::NAMESPACE_RSS_090, 'link')) { $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], self::CONSTRUCT_IRI, $this->get_base($links[0])); } if ($links = $this->get_channel_tags(self::NAMESPACE_RSS_20, 'link')) { $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], self::CONSTRUCT_IRI, $this->get_base($links[0])); } $keys = array_keys($this->data['links']); foreach ($keys as $key) { if ($this->registry->call(Misc::class, 'is_isegment_nz_nc', [$key])) { if (isset($this->data['links'][self::IANA_LINK_RELATIONS_REGISTRY . $key])) { $this->data['links'][self::IANA_LINK_RELATIONS_REGISTRY . $key] = array_merge($this->data['links'][$key], $this->data['links'][self::IANA_LINK_RELATIONS_REGISTRY . $key]); $this->data['links'][$key] = &$this->data['links'][self::IANA_LINK_RELATIONS_REGISTRY . $key]; } else { $this->data['links'][self::IANA_LINK_RELATIONS_REGISTRY . $key] = &$this->data['links'][$key]; } } elseif (substr($key, 0, 41) === self::IANA_LINK_RELATIONS_REGISTRY) { $this->data['links'][substr($key, 41)] = &$this->data['links'][$key]; } $this->data['links'][$key] = array_unique($this->data['links'][$key]); } } if (isset($this->data['headers']['link'])) { $link_headers = $this->data['headers']['link']; if (is_array($link_headers)) { $link_headers = implode(',', $link_headers); } // https://datatracker.ietf.org/doc/html/rfc8288 if (is_string($link_headers) && preg_match_all('/<(?P<uri>[^>]+)>\s*;\s*rel\s*=\s*(?P<quote>"?)' . preg_quote($rel) . '(?P=quote)\s*(?=,|$)/i', $link_headers, $matches)) { return $matches['uri']; } } if (isset($this->data['links'][$rel])) { return $this->data['links'][$rel]; } return null; } /** * @return ?array<Response> */ public function get_all_discovered_feeds() { return $this->all_discovered_feeds; } /** * Get the content for the item * * Uses `<atom:subtitle>`, `<atom:tagline>`, `<description>`, * `<dc:description>`, `<itunes:summary>` or `<itunes:subtitle>` * * @since 1.0 (previously called `get_feed_description()` since 0.8) * @return string|null */ public function get_description() { if ($return = $this->get_channel_tags(self::NAMESPACE_ATOM_10, 'subtitle')) { return $this->sanitize($return[0]['data'], $this->registry->call(Misc::class, 'atom_10_construct_type', [$return[0]['attribs']]), $this->get_base($return[0])); } elseif ($return = $this->get_channel_tags(self::NAMESPACE_ATOM_03, 'tagline')) { return $this->sanitize($return[0]['data'], $this->registry->call(Misc::class, 'atom_03_construct_type', [$return[0]['attribs']]), $this->get_base($return[0])); } elseif ($return = $this->get_channel_tags(self::NAMESPACE_RSS_10, 'description')) { return $this->sanitize($return[0]['data'], self::CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); } elseif ($return = $this->get_channel_tags(self::NAMESPACE_RSS_090, 'description')) { return $this->sanitize($return[0]['data'], self::CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); } elseif ($return = $this->get_channel_tags(self::NAMESPACE_RSS_20, 'description')) { return $this->sanitize($return[0]['data'], self::CONSTRUCT_HTML, $this->get_base($return[0])); } elseif ($return = $this->get_channel_tags(self::NAMESPACE_DC_11, 'description')) { return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT); } elseif ($return = $this->get_channel_tags(self::NAMESPACE_DC_10, 'description')) { return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT); } elseif ($return = $this->get_channel_tags(self::NAMESPACE_ITUNES, 'summary')) { return $this->sanitize($return[0]['data'], self::CONSTRUCT_HTML, $this->get_base($return[0])); } elseif ($return = $this->get_channel_tags(self::NAMESPACE_ITUNES, 'subtitle')) { return $this->sanitize($return[0]['data'], self::CONSTRUCT_HTML, $this->get_base($return[0])); } return null; } /** * Get the copyright info for the feed * * Uses `<atom:rights>`, `<atom:copyright>` or `<dc:rights>` * * @since 1.0 (previously called `get_feed_copyright()` since 0.8) * @return string|null */ public function get_copyright() { if ($return = $this->get_channel_tags(self::NAMESPACE_ATOM_10, 'rights')) { return $this->sanitize($return[0]['data'], $this->registry->call(Misc::class, 'atom_10_construct_type', [$return[0]['attribs']]), $this->get_base($return[0])); } elseif ($return = $this->get_channel_tags(self::NAMESPACE_ATOM_03, 'copyright')) { return $this->sanitize($return[0]['data'], $this->registry->call(Misc::class, 'atom_03_construct_type', [$return[0]['attribs']]), $this->get_base($return[0])); } elseif ($return = $this->get_channel_tags(self::NAMESPACE_RSS_20, 'copyright')) { return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT); } elseif ($return = $this->get_channel_tags(self::NAMESPACE_DC_11, 'rights')) { return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT); } elseif ($return = $this->get_channel_tags(self::NAMESPACE_DC_10, 'rights')) { return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT); } return null; } /** * Get the language for the feed * * Uses `<language>`, `<dc:language>`, or @xml_lang * * @since 1.0 (previously called `get_feed_language()` since 0.8) * @return string|null */ public function get_language() { if ($return = $this->get_channel_tags(self::NAMESPACE_RSS_20, 'language')) { return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT); } elseif ($return = $this->get_channel_tags(self::NAMESPACE_DC_11, 'language')) { return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT); } elseif ($return = $this->get_channel_tags(self::NAMESPACE_DC_10, 'language')) { return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT); } elseif (isset($this->data['child'][self::NAMESPACE_ATOM_10]['feed'][0]['xml_lang'])) { return $this->sanitize($this->data['child'][self::NAMESPACE_ATOM_10]['feed'][0]['xml_lang'], self::CONSTRUCT_TEXT); } elseif (isset($this->data['child'][self::NAMESPACE_ATOM_03]['feed'][0]['xml_lang'])) { return $this->sanitize($this->data['child'][self::NAMESPACE_ATOM_03]['feed'][0]['xml_lang'], self::CONSTRUCT_TEXT); } elseif (isset($this->data['child'][self::NAMESPACE_RDF]['RDF'][0]['xml_lang'])) { return $this->sanitize($this->data['child'][self::NAMESPACE_RDF]['RDF'][0]['xml_lang'], self::CONSTRUCT_TEXT); } elseif (isset($this->data['headers']['content-language'])) { return $this->sanitize($this->data['headers']['content-language'], self::CONSTRUCT_TEXT); } return null; } /** * Get the latitude coordinates for the item * * Compatible with the W3C WGS84 Basic Geo and GeoRSS specifications * * Uses `<geo:lat>` or `<georss:point>` * * @since 1.0 * @link http://www.w3.org/2003/01/geo/ W3C WGS84 Basic Geo * @link http://www.georss.org/ GeoRSS * @return float|null */ public function get_latitude() { if ($return = $this->get_channel_tags(self::NAMESPACE_W3C_BASIC_GEO, 'lat')) { return (float) $return[0]['data']; } elseif (($return = $this->get_channel_tags(self::NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match)) { return (float) $match[1]; } return null; } /** * Get the longitude coordinates for the feed * * Compatible with the W3C WGS84 Basic Geo and GeoRSS specifications * * Uses `<geo:long>`, `<geo:lon>` or `<georss:point>` * * @since 1.0 * @link http://www.w3.org/2003/01/geo/ W3C WGS84 Basic Geo * @link http://www.georss.org/ GeoRSS * @return float|null */ public function get_longitude() { if ($return = $this->get_channel_tags(self::NAMESPACE_W3C_BASIC_GEO, 'long')) { return (float) $return[0]['data']; } elseif ($return = $this->get_channel_tags(self::NAMESPACE_W3C_BASIC_GEO, 'lon')) { return (float) $return[0]['data']; } elseif (($return = $this->get_channel_tags(self::NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match)) { return (float) $match[2]; } return null; } /** * Get the feed logo's title * * RSS 0.9.0, 1.0 and 2.0 feeds are allowed to have a "feed logo" title. * * Uses `<image><title>` or `<image><dc:title>` * * @return string|null */ public function get_image_title() { if ($return = $this->get_image_tags(self::NAMESPACE_RSS_10, 'title')) { return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT); } elseif ($return = $this->get_image_tags(self::NAMESPACE_RSS_090, 'title')) { return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT); } elseif ($return = $this->get_image_tags(self::NAMESPACE_RSS_20, 'title')) { return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT); } elseif ($return = $this->get_image_tags(self::NAMESPACE_DC_11, 'title')) { return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT); } elseif ($return = $this->get_image_tags(self::NAMESPACE_DC_10, 'title')) { return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT); } return null; } /** * Get the feed logo's URL * * RSS 0.9.0, 2.0, Atom 1.0, and feeds with iTunes RSS tags are allowed to * have a "feed logo" URL. This points directly to the image itself. * * Uses `<itunes:image>`, `<atom:logo>`, `<atom:icon>`, * `<image><title>` or `<image><dc:title>` * * @return string|null */ public function get_image_url() { if ($return = $this->get_channel_tags(self::NAMESPACE_ITUNES, 'image')) { return $this->sanitize($return[0]['attribs']['']['href'], self::CONSTRUCT_IRI); } elseif ($return = $this->get_channel_tags(self::NAMESPACE_ATOM_10, 'logo')) { return $this->sanitize($return[0]['data'], self::CONSTRUCT_IRI, $this->get_base($return[0])); } elseif ($return = $this->get_channel_tags(self::NAMESPACE_ATOM_10, 'icon')) { return $this->sanitize($return[0]['data'], self::CONSTRUCT_IRI, $this->get_base($return[0])); } elseif ($return = $this->get_image_tags(self::NAMESPACE_RSS_10, 'url')) { return $this->sanitize($return[0]['data'], self::CONSTRUCT_IRI, $this->get_base($return[0])); } elseif ($return = $this->get_image_tags(self::NAMESPACE_RSS_090, 'url')) { return $this->sanitize($return[0]['data'], self::CONSTRUCT_IRI, $this->get_base($return[0])); } elseif ($return = $this->get_image_tags(self::NAMESPACE_RSS_20, 'url')) { return $this->sanitize($return[0]['data'], self::CONSTRUCT_IRI, $this->get_base($return[0])); } return null; } /** * Get the feed logo's link * * RSS 0.9.0, 1.0 and 2.0 feeds are allowed to have a "feed logo" link. This * points to a human-readable page that the image should link to. * * Uses `<itunes:image>`, `<atom:logo>`, `<atom:icon>`, * `<image><title>` or `<image><dc:title>` * * @return string|null */ public function get_image_link() { if ($return = $this->get_image_tags(self::NAMESPACE_RSS_10, 'link')) { return $this->sanitize($return[0]['data'], self::CONSTRUCT_IRI, $this->get_base($return[0])); } elseif ($return = $this->get_image_tags(self::NAMESPACE_RSS_090, 'link')) { return $this->sanitize($return[0]['data'], self::CONSTRUCT_IRI, $this->get_base($return[0])); } elseif ($return = $this->get_image_tags(self::NAMESPACE_RSS_20, 'link')) { return $this->sanitize($return[0]['data'], self::CONSTRUCT_IRI, $this->get_base($return[0])); } return null; } /** * Get the feed logo's link * * RSS 2.0 feeds are allowed to have a "feed logo" width. * * Uses `<image><width>` or defaults to 88 if no width is specified and * the feed is an RSS 2.0 feed. * * @return int|null */ public function get_image_width() { if ($return = $this->get_image_tags(self::NAMESPACE_RSS_20, 'width')) { return intval($return[0]['data']); } elseif ($this->get_type() & self::TYPE_RSS_SYNDICATION && $this->get_image_tags(self::NAMESPACE_RSS_20, 'url')) { return 88; } return null; } /** * Get the feed logo's height * * RSS 2.0 feeds are allowed to have a "feed logo" height. * * Uses `<image><height>` or defaults to 31 if no height is specified and * the feed is an RSS 2.0 feed. * * @return int|null */ public function get_image_height() { if ($return = $this->get_image_tags(self::NAMESPACE_RSS_20, 'height')) { return intval($return[0]['data']); } elseif ($this->get_type() & self::TYPE_RSS_SYNDICATION && $this->get_image_tags(self::NAMESPACE_RSS_20, 'url')) { return 31; } return null; } /** * Get the number of items in the feed * * This is well-suited for {@link http://php.net/for for()} loops with * {@see get_item()} * * @param int $max Maximum value to return. 0 for no limit * @return int Number of items in the feed */ public function get_item_quantity(int $max = 0) { $qty = count($this->get_items()); if ($max === 0) { return $qty; } return min($qty, $max); } /** * Get a single item from the feed * * This is better suited for {@link http://php.net/for for()} loops, whereas * {@see get_items()} is better suited for * {@link http://php.net/foreach foreach()} loops. * * @see get_item_quantity() * @since Beta 2 * @param int $key The item that you want to return. Remember that arrays begin with 0, not 1 * @return Item|null */ public function get_item(int $key = 0) { $items = $this->get_items(); if (isset($items[$key])) { return $items[$key]; } return null; } /** * Get all items from the feed * * This is better suited for {@link http://php.net/for for()} loops, whereas * {@see get_items()} is better suited for * {@link http://php.net/foreach foreach()} loops. * * @see get_item_quantity * @since Beta 2 * @param int $start Index to start at * @param int $end Number of items to return. 0 for all items after `$start` * @return Item[] List of {@see Item} objects */ public function get_items(int $start = 0, int $end = 0) { if (!isset($this->data['items'])) { if (!empty($this->multifeed_objects)) { $this->data['items'] = SimplePie::merge_items($this->multifeed_objects, $start, $end, $this->item_limit); if (empty($this->data['items'])) { return []; } return $this->data['items']; } $this->data['items'] = []; if ($items = $this->get_feed_tags(self::NAMESPACE_ATOM_10, 'entry')) { $keys = array_keys($items); foreach ($keys as $key) { $this->data['items'][] = $this->make_item($items[$key]); } } if ($items = $this->get_feed_tags(self::NAMESPACE_ATOM_03, 'entry')) { $keys = array_keys($items); foreach ($keys as $key) { $this->data['items'][] = $this->make_item($items[$key]); } } if ($items = $this->get_feed_tags(self::NAMESPACE_RSS_10, 'item')) { $keys = array_keys($items); foreach ($keys as $key) { $this->data['items'][] = $this->make_item($items[$key]); } } if ($items = $this->get_feed_tags(self::NAMESPACE_RSS_090, 'item')) { $keys = array_keys($items); foreach ($keys as $key) { $this->data['items'][] = $this->make_item($items[$key]); } } if ($items = $this->get_channel_tags(self::NAMESPACE_RSS_20, 'item')) { $keys = array_keys($items); foreach ($keys as $key) { $this->data['items'][] = $this->make_item($items[$key]); } } } if (empty($this->data['items'])) { return []; } if ($this->order_by_date) { if (!isset($this->data['ordered_items'])) { $this->data['ordered_items'] = $this->data['items']; usort($this->data['ordered_items'], [get_class($this), 'sort_items']); } $items = $this->data['ordered_items']; } else { $items = $this->data['items']; } // Slice the data as desired if ($end === 0) { return array_slice($items, $start); } return array_slice($items, $start, $end); } /** * Set the favicon handler * * @deprecated Use your own favicon handling instead * @param string|false $page * @return bool */ public function set_favicon_handler($page = false, string $qs = 'i') { trigger_error('Favicon handling has been removed since SimplePie 1.3, please use your own handling', \E_USER_DEPRECATED); return false; } /** * Get the favicon for the current feed * * @deprecated Use your own favicon handling instead * @return string|bool */ public function get_favicon() { trigger_error('Favicon handling has been removed since SimplePie 1.3, please use your own handling', \E_USER_DEPRECATED); if (($url = $this->get_link()) !== null) { return 'https://www.google.com/s2/favicons?domain=' . urlencode($url); } return false; } /** * Magic method handler * * @param string $method Method name * @param array<mixed> $args Arguments to the method * @return mixed */ public function __call(string $method, array $args) { if (strpos($method, 'subscribe_') === 0) { trigger_error('subscribe_*() has been deprecated since SimplePie 1.3, implement the callback yourself', \E_USER_DEPRECATED); return ''; } if ($method === 'enable_xml_dump') { trigger_error('enable_xml_dump() has been deprecated since SimplePie 1.3, use get_raw_data() instead', \E_USER_DEPRECATED); return false; } $class = get_class($this); $trace = debug_backtrace(); // phpcs:ignore PHPCompatibility.FunctionUse.ArgumentFunctionsReportCurrentValue.NeedsInspection $file = $trace[0]['file'] ?? ''; $line = $trace[0]['line'] ?? ''; throw new SimplePieException("Call to undefined method $class::$method() in $file on line $line"); } /** * Item factory * * @param array<string, mixed> $data */ private function make_item(array $data): Item { $item = $this->registry->create(Item::class, [$this, $data]); $item->set_sanitize($this->sanitize); return $item; } /** * Sorting callback for items * * @access private * @param Item $a * @param Item $b * @return -1|0|1 */ public static function sort_items(Item $a, Item $b) { $a_date = $a->get_date('U'); $b_date = $b->get_date('U'); if ($a_date && $b_date) { return $a_date > $b_date ? -1 : 1; } // Sort items without dates to the top. if ($a_date) { return 1; } if ($b_date) { return -1; } return 0; } /** * Merge items from several feeds into one * * If you're merging multiple feeds together, they need to all have dates * for the items or else SimplePie will refuse to sort them. * * @link http://simplepie.org/wiki/tutorial/sort_multiple_feeds_by_time_and_date#if_feeds_require_separate_per-feed_settings * @param array<SimplePie> $urls List of SimplePie feed objects to merge * @param int $start Starting item * @param int $end Number of items to return * @param int $limit Maximum number of items per feed * @return array<Item> */ public static function merge_items(array $urls, int $start = 0, int $end = 0, int $limit = 0) { if (count($urls) > 0) { $items = []; foreach ($urls as $arg) { if ($arg instanceof SimplePie) { $items = array_merge($items, $arg->get_items(0, $limit)); // @phpstan-ignore-next-line Enforce PHPDoc type. } else { trigger_error('Arguments must be SimplePie objects', E_USER_WARNING); } } usort($items, [get_class($urls[0]), 'sort_items']); if ($end === 0) { return array_slice($items, $start); } return array_slice($items, $start, $end); } trigger_error('Cannot merge zero SimplePie objects', E_USER_WARNING); return []; } /** * Store PubSubHubbub links as headers * * There is no way to find PuSH links in the body of a microformats feed, * so they are added to the headers when found, to be used later by get_links. */ private function store_links(Response $file, ?string $hub, ?string $self): Response { $linkHeaderLine = $file->get_header_line('link'); $linkHeader = $file->get_header('link'); if ($hub && !preg_match('/rel=hub/', $linkHeaderLine)) { $linkHeader[] = '<'.$hub.'>; rel=hub'; } if ($self && !preg_match('/rel=self/', $linkHeaderLine)) { $linkHeader[] = '<'.$self.'>; rel=self'; } if (count($linkHeader) > 0) { $file = $file->with_header('link', $linkHeader); } return $file; } /** * Get a DataCache * * @param string $feed_url Only needed for BC, can be removed in SimplePie 2.0.0 * * @return DataCache */ private function get_cache(string $feed_url = ''): DataCache { if ($this->cache === null) { // @trigger_error(sprintf('Not providing as PSR-16 cache implementation is deprecated since SimplePie 1.8.0, please use "SimplePie\SimplePie::set_cache()".'), \E_USER_DEPRECATED); $cache = $this->registry->call(Cache::class, 'get_handler', [ $this->cache_location, $this->get_cache_filename($feed_url), Base::TYPE_FEED ]); return new BaseDataCache($cache); } return $this->cache; } /** * Get a HTTP client */ private function get_http_client(): Client { if ($this->http_client === null) { $this->http_client = new FileClient( $this->get_registry(), [ 'timeout' => $this->timeout, 'redirects' => 5, 'useragent' => $this->useragent, 'force_fsockopen' => $this->force_fsockopen, 'curl_options' => $this->curl_options, ] ); $this->http_client_injected = true; } return $this->http_client; } } class_alias('SimplePie\SimplePie', 'SimplePie'); ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Credit.php������������������������������������������������������������������������������������������0000644�����������������00000004140�15250524467�0006500 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php // SPDX-FileCopyrightText: 2004-2023 Ryan Parman, Sam Sneddon, Ryan McCue // SPDX-License-Identifier: BSD-3-Clause declare(strict_types=1); namespace SimplePie; /** * Handles `<media:credit>` as defined in Media RSS * * Used by {@see \SimplePie\Enclosure::get_credit()} and {@see \SimplePie\Enclosure::get_credits()} * * This class can be overloaded with {@see \SimplePie\SimplePie::set_credit_class()} */ class Credit { /** * Credited role * * @var ?string * @see get_role() */ public $role; /** * Organizational scheme * * @var ?string * @see get_scheme() */ public $scheme; /** * Credited name * * @var ?string * @see get_name() */ public $name; /** * Constructor, used to input the data * * For documentation on all the parameters, see the corresponding * properties and their accessors */ public function __construct( ?string $role = null, ?string $scheme = null, ?string $name = null ) { $this->role = $role; $this->scheme = $scheme; $this->name = $name; } /** * String-ified version * * @return string */ public function __toString() { // There is no $this->data here return md5(serialize($this)); } /** * Get the role of the person receiving credit * * @return string|null */ public function get_role() { if ($this->role !== null) { return $this->role; } return null; } /** * Get the organizational scheme * * @return string|null */ public function get_scheme() { if ($this->scheme !== null) { return $this->scheme; } return null; } /** * Get the credited person/entity's name * * @return string|null */ public function get_name() { if ($this->name !== null) { return $this->name; } return null; } } class_alias('SimplePie\Credit', 'SimplePie_Credit'); ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Enclosure.php���������������������������������������������������������������������������������������0000644�����������������00000076616�15250524467�0007246 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php // SPDX-FileCopyrightText: 2004-2023 Ryan Parman, Sam Sneddon, Ryan McCue // SPDX-License-Identifier: BSD-3-Clause declare(strict_types=1); namespace SimplePie; /** * Handles everything related to enclosures (including Media RSS and iTunes RSS) * * Used by {@see \SimplePie\Item::get_enclosure()} and {@see \SimplePie\Item::get_enclosures()} * * This class can be overloaded with {@see \SimplePie\SimplePie::set_enclosure_class()} */ class Enclosure { /** * @var ?string * @see get_bitrate() */ public $bitrate; /** * @var Caption[]|null * @see get_captions() */ public $captions; /** * @var Category[]|null * @see get_categories() */ public $categories; /** * @var ?int * @see get_channels() */ public $channels; /** * @var ?Copyright * @see get_copyright() */ public $copyright; /** * @var Credit[]|null * @see get_credits() */ public $credits; /** * @var ?string * @see get_description() */ public $description; /** * @var ?int * @see get_duration() */ public $duration; /** * @var ?string * @see get_expression() */ public $expression; /** * @var ?string * @see get_framerate() */ public $framerate; /** * @var ?string * @see get_handler() */ public $handler; /** * @var string[]|null * @see get_hashes() */ public $hashes; /** * @var ?string * @see get_height() */ public $height; /** * @deprecated * @var null */ public $javascript; /** * @var string[]|null * @see get_keywords() */ public $keywords; /** * @var ?string * @see get_language() */ public $lang; /** * @var ?int * @see get_length() */ public $length; /** * @var ?string * @see get_link() */ public $link; /** * @var ?string * @see get_medium() */ public $medium; /** * @var ?string * @see get_player() */ public $player; /** * @var Rating[]|null * @see get_ratings() */ public $ratings; /** * @var ?Restriction[] * @see get_restrictions() */ public $restrictions; /** * @var ?string * @see get_sampling_rate() */ public $samplingrate; /** * @var string[]|null * @see get_thumbnails() */ public $thumbnails; /** * @var ?string * @see get_title() */ public $title; /** * @var ?string * @see get_type() */ public $type; /** * @var ?string * @see get_width() */ public $width; /** * Constructor, used to input the data * * For documentation on all the parameters, see the corresponding * properties and their accessors * * @uses idn_to_ascii If available, this will convert an IDN * * @param null $javascript * @param Caption[]|null $captions * @param Category[]|null $categories * @param Credit[]|null $credits * @param string[]|null $hashes * @param string[]|null $keywords * @param Rating[]|null $ratings * @param Restriction[]|null $restrictions * @param string[]|null $thumbnails */ public function __construct( ?string $link = null, ?string $type = null, ?int $length = null, $javascript = null, ?string $bitrate = null, ?array $captions = null, ?array $categories = null, ?int $channels = null, ?Copyright $copyright = null, ?array $credits = null, ?string $description = null, ?int $duration = null, ?string $expression = null, ?string $framerate = null, ?array $hashes = null, ?string $height = null, ?array $keywords = null, ?string $lang = null, ?string $medium = null, ?string $player = null, ?array $ratings = null, ?array $restrictions = null, ?string $samplingrate = null, ?array $thumbnails = null, ?string $title = null, ?string $width = null ) { $this->bitrate = $bitrate; $this->captions = $captions; $this->categories = $categories; $this->channels = $channels; $this->copyright = $copyright; $this->credits = $credits; $this->description = $description; $this->duration = $duration; $this->expression = $expression; $this->framerate = $framerate; $this->hashes = $hashes; $this->height = $height; $this->keywords = $keywords; $this->lang = $lang; $this->length = $length; $this->link = $link; $this->medium = $medium; $this->player = $player; $this->ratings = $ratings; $this->restrictions = $restrictions; $this->samplingrate = $samplingrate; $this->thumbnails = $thumbnails; $this->title = $title; $this->type = $type; $this->width = $width; if (function_exists('idn_to_ascii')) { $parsed = \SimplePie\Misc::parse_url($link ?? ''); if ($parsed['authority'] !== '' && !ctype_print($parsed['authority'])) { $authority = (string) \idn_to_ascii($parsed['authority'], \IDNA_NONTRANSITIONAL_TO_ASCII, \INTL_IDNA_VARIANT_UTS46); $this->link = \SimplePie\Misc::compress_parse_url($parsed['scheme'], $authority, $parsed['path'], $parsed['query'], $parsed['fragment']); } } $this->handler = $this->get_handler(); // Needs to load last } /** * String-ified version * * @return string */ public function __toString() { // There is no $this->data here return md5(serialize($this)); } /** * Get the bitrate * * @return string|null */ public function get_bitrate() { if ($this->bitrate !== null) { return $this->bitrate; } return null; } /** * Get a single caption * * @param int $key * @return \SimplePie\Caption|null */ public function get_caption(int $key = 0) { $captions = $this->get_captions(); if (isset($captions[$key])) { return $captions[$key]; } return null; } /** * Get all captions * * @return Caption[]|null */ public function get_captions() { if ($this->captions !== null) { return $this->captions; } return null; } /** * Get a single category * * @param int $key * @return \SimplePie\Category|null */ public function get_category(int $key = 0) { $categories = $this->get_categories(); if (isset($categories[$key])) { return $categories[$key]; } return null; } /** * Get all categories * * @return \SimplePie\Category[]|null */ public function get_categories() { if ($this->categories !== null) { return $this->categories; } return null; } /** * Get the number of audio channels * * @return int|null */ public function get_channels() { if ($this->channels !== null) { return $this->channels; } return null; } /** * Get the copyright information * * @return \SimplePie\Copyright|null */ public function get_copyright() { if ($this->copyright !== null) { return $this->copyright; } return null; } /** * Get a single credit * * @param int $key * @return \SimplePie\Credit|null */ public function get_credit(int $key = 0) { $credits = $this->get_credits(); if (isset($credits[$key])) { return $credits[$key]; } return null; } /** * Get all credits * * @return Credit[]|null */ public function get_credits() { if ($this->credits !== null) { return $this->credits; } return null; } /** * Get the description of the enclosure * * @return string|null */ public function get_description() { if ($this->description !== null) { return $this->description; } return null; } /** * Get the duration of the enclosure * * @param bool $convert Convert seconds into hh:mm:ss * @return string|int|null 'hh:mm:ss' string if `$convert` was specified, otherwise integer (or null if none found) */ public function get_duration(bool $convert = false) { if ($this->duration !== null) { if ($convert) { $time = \SimplePie\Misc::time_hms($this->duration); return $time; } return $this->duration; } return null; } /** * Get the expression * * @return string Probably one of 'sample', 'full', 'nonstop', 'clip'. Defaults to 'full' */ public function get_expression() { if ($this->expression !== null) { return $this->expression; } return 'full'; } /** * Get the file extension * * @return string|null */ public function get_extension() { if ($this->link !== null) { $url = \SimplePie\Misc::parse_url($this->link); if ($url['path'] !== '') { return pathinfo($url['path'], PATHINFO_EXTENSION); } } return null; } /** * Get the framerate (in frames-per-second) * * @return string|null */ public function get_framerate() { if ($this->framerate !== null) { return $this->framerate; } return null; } /** * Get the preferred handler * * @return string|null One of 'flash', 'fmedia', 'quicktime', 'wmedia', 'mp3' */ public function get_handler() { return $this->get_real_type(true); } /** * Get a single hash * * @link http://www.rssboard.org/media-rss#media-hash * @param int $key * @return string|null Hash as per `media:hash`, prefixed with "$algo:" */ public function get_hash(int $key = 0) { $hashes = $this->get_hashes(); if (isset($hashes[$key])) { return $hashes[$key]; } return null; } /** * Get all credits * * @return string[]|null Array of strings, see {@see get_hash()} */ public function get_hashes() { if ($this->hashes !== null) { return $this->hashes; } return null; } /** * Get the height * * @return string|null */ public function get_height() { if ($this->height !== null) { return $this->height; } return null; } /** * Get the language * * @link http://tools.ietf.org/html/rfc3066 * @return string|null Language code as per RFC 3066 */ public function get_language() { if ($this->lang !== null) { return $this->lang; } return null; } /** * Get a single keyword * * @param int $key * @return string|null */ public function get_keyword(int $key = 0) { $keywords = $this->get_keywords(); if (isset($keywords[$key])) { return $keywords[$key]; } return null; } /** * Get all keywords * * @return string[]|null */ public function get_keywords() { if ($this->keywords !== null) { return $this->keywords; } return null; } /** * Get length * * @return ?int Length in bytes */ public function get_length() { if ($this->length !== null) { return $this->length; } return null; } /** * Get the URL * * @return string|null */ public function get_link() { if ($this->link !== null) { return $this->link; } return null; } /** * Get the medium * * @link http://www.rssboard.org/media-rss#media-content * @return string|null Should be one of 'image', 'audio', 'video', 'document', 'executable' */ public function get_medium() { if ($this->medium !== null) { return $this->medium; } return null; } /** * Get the player URL * * Typically the same as {@see get_permalink()} * @return string|null Player URL */ public function get_player() { if ($this->player !== null) { return $this->player; } return null; } /** * Get a single rating * * @param int $key * @return \SimplePie\Rating|null */ public function get_rating(int $key = 0) { $ratings = $this->get_ratings(); if (isset($ratings[$key])) { return $ratings[$key]; } return null; } /** * Get all ratings * * @return Rating[]|null */ public function get_ratings() { if ($this->ratings !== null) { return $this->ratings; } return null; } /** * Get a single restriction * * @param int $key * @return \SimplePie\Restriction|null */ public function get_restriction(int $key = 0) { $restrictions = $this->get_restrictions(); if (isset($restrictions[$key])) { return $restrictions[$key]; } return null; } /** * Get all restrictions * * @return Restriction[]|null */ public function get_restrictions() { if ($this->restrictions !== null) { return $this->restrictions; } return null; } /** * Get the sampling rate (in kHz) * * @return string|null */ public function get_sampling_rate() { if ($this->samplingrate !== null) { return $this->samplingrate; } return null; } /** * Get the file size (in MiB) * * @return float|null File size in mebibytes (1048 bytes) */ public function get_size() { $length = $this->get_length(); if ($length !== null) { return round($length / 1048576, 2); } return null; } /** * Get a single thumbnail * * @param int $key * @return string|null Thumbnail URL */ public function get_thumbnail(int $key = 0) { $thumbnails = $this->get_thumbnails(); if (isset($thumbnails[$key])) { return $thumbnails[$key]; } return null; } /** * Get all thumbnails * * @return string[]|null Array of thumbnail URLs */ public function get_thumbnails() { if ($this->thumbnails !== null) { return $this->thumbnails; } return null; } /** * Get the title * * @return string|null */ public function get_title() { if ($this->title !== null) { return $this->title; } return null; } /** * Get mimetype of the enclosure * * @see get_real_type() * @return string|null MIME type */ public function get_type() { if ($this->type !== null) { return $this->type; } return null; } /** * Get the width * * @return string|null */ public function get_width() { if ($this->width !== null) { return $this->width; } return null; } /** * Embed the enclosure using `<embed>` * * @deprecated Use the second parameter to {@see embed} instead * * @param array<string, mixed>|string $options See first parameter to {@see embed} * @return string HTML string to output */ public function native_embed($options = '') { return $this->embed($options, true); } /** * Embed the enclosure using Javascript * * `$options` is an array or comma-separated key:value string, with the * following properties: * * - `alt` (string): Alternate content for when an end-user does not have * the appropriate handler installed or when a file type is * unsupported. Can be any text or HTML. Defaults to blank. * - `altclass` (string): If a file type is unsupported, the end-user will * see the alt text (above) linked directly to the content. That link * will have this value as its class name. Defaults to blank. * - `audio` (string): This is an image that should be used as a * placeholder for audio files before they're loaded (QuickTime-only). * Can be any relative or absolute URL. Defaults to blank. * - `bgcolor` (string): The background color for the media, if not * already transparent. Defaults to `#ffffff`. * - `height` (integer): The height of the embedded media. Accepts any * numeric pixel value (such as `360`) or `auto`. Defaults to `auto`, * and it is recommended that you use this default. * - `loop` (boolean): Do you want the media to loop when it's done? * Defaults to `false`. * - `mediaplayer` (string): The location of the included * `mediaplayer.swf` file. This allows for the playback of Flash Video * (`.flv`) files, and is the default handler for non-Odeo MP3's. * Defaults to blank. * - `video` (string): This is an image that should be used as a * placeholder for video files before they're loaded (QuickTime-only). * Can be any relative or absolute URL. Defaults to blank. * - `width` (integer): The width of the embedded media. Accepts any * numeric pixel value (such as `480`) or `auto`. Defaults to `auto`, * and it is recommended that you use this default. * - `widescreen` (boolean): Is the enclosure widescreen or standard? * This applies only to video enclosures, and will automatically resize * the content appropriately. Defaults to `false`, implying 4:3 mode. * * Note: Non-widescreen (4:3) mode with `width` and `height` set to `auto` * will default to 480x360 video resolution. Widescreen (16:9) mode with * `width` and `height` set to `auto` will default to 480x270 video resolution. * * @todo If the dimensions for media:content are defined, use them when width/height are set to 'auto'. * @param array<string, mixed>|string $options Comma-separated key:value list, or array * @param bool $native Use `<embed>` * @return string HTML string to output */ public function embed($options = '', bool $native = false) { // Set up defaults $audio = ''; $video = ''; $alt = ''; $altclass = ''; $loop = 'false'; $width = 'auto'; $height = 'auto'; $bgcolor = '#ffffff'; $mediaplayer = ''; $widescreen = false; $handler = $this->get_handler(); $type = $this->get_real_type(); $placeholder = ''; // Process options and reassign values as necessary if (is_array($options)) { extract($options); } else { $options = explode(',', $options); foreach ($options as $option) { $opt = explode(':', $option, 2); if (isset($opt[0], $opt[1])) { $opt[0] = trim($opt[0]); $opt[1] = trim($opt[1]); switch ($opt[0]) { case 'audio': $audio = $opt[1]; break; case 'video': $video = $opt[1]; break; case 'alt': $alt = $opt[1]; break; case 'altclass': $altclass = $opt[1]; break; case 'loop': $loop = $opt[1]; break; case 'width': $width = $opt[1]; break; case 'height': $height = $opt[1]; break; case 'bgcolor': $bgcolor = $opt[1]; break; case 'mediaplayer': $mediaplayer = $opt[1]; break; case 'widescreen': $widescreen = $opt[1]; break; } } } } $mime = explode('/', (string) $type, 2); $mime = $mime[0]; // Process values for 'auto' if ($width === 'auto') { if ($mime === 'video') { if ($height === 'auto') { $width = 480; } elseif ($widescreen) { $width = round((intval($height) / 9) * 16); } else { $width = round((intval($height) / 3) * 4); } } else { $width = '100%'; } } if ($height === 'auto') { if ($mime === 'audio') { $height = 0; } elseif ($mime === 'video') { if ($width === 'auto') { if ($widescreen) { $height = 270; } else { $height = 360; } } elseif ($widescreen) { $height = round((intval($width) / 16) * 9); } else { $height = round((intval($width) / 4) * 3); } } else { $height = 376; } } elseif ($mime === 'audio') { $height = 0; } // Set proper placeholder value if ($mime === 'audio') { $placeholder = $audio; } elseif ($mime === 'video') { $placeholder = $video; } $embed = ''; // Flash if ($handler === 'flash') { if ($native) { $embed .= "<embed src=\"" . $this->get_link() . "\" pluginspage=\"http://adobe.com/go/getflashplayer\" type=\"$type\" quality=\"high\" width=\"$width\" height=\"$height\" bgcolor=\"$bgcolor\" loop=\"$loop\"></embed>"; } else { $embed .= "<script type='text/javascript'>embed_flash('$bgcolor', '$width', '$height', '" . $this->get_link() . "', '$loop', '$type');</script>"; } } // Flash Media Player file types. // Preferred handler for MP3 file types. elseif ($handler === 'fmedia' || ($handler === 'mp3' && $mediaplayer !== '')) { if (is_numeric($height)) { $height += 20; } if ($native) { $embed .= "<embed src=\"$mediaplayer\" pluginspage=\"http://adobe.com/go/getflashplayer\" type=\"application/x-shockwave-flash\" quality=\"high\" width=\"$width\" height=\"$height\" wmode=\"transparent\" flashvars=\"file=" . rawurlencode($this->get_link().'?file_extension=.'.$this->get_extension()) . "&autostart=false&repeat=$loop&showdigits=true&showfsbutton=false\"></embed>"; } else { $embed .= "<script type='text/javascript'>embed_flv('$width', '$height', '" . rawurlencode($this->get_link().'?file_extension=.'.$this->get_extension()) . "', '$placeholder', '$loop', '$mediaplayer');</script>"; } } // QuickTime 7 file types. Need to test with QuickTime 6. // Only handle MP3's if the Flash Media Player is not present. elseif ($handler === 'quicktime' || ($handler === 'mp3' && $mediaplayer === '')) { if (is_numeric($height)) { $height += 16; } if ($native) { if ($placeholder !== '') { $embed .= "<embed type=\"$type\" style=\"cursor:hand; cursor:pointer;\" href=\"" . $this->get_link() . "\" src=\"$placeholder\" width=\"$width\" height=\"$height\" autoplay=\"false\" target=\"myself\" controller=\"false\" loop=\"$loop\" scale=\"aspect\" bgcolor=\"$bgcolor\" pluginspage=\"http://apple.com/quicktime/download/\"></embed>"; } else { $embed .= "<embed type=\"$type\" style=\"cursor:hand; cursor:pointer;\" src=\"" . $this->get_link() . "\" width=\"$width\" height=\"$height\" autoplay=\"false\" target=\"myself\" controller=\"true\" loop=\"$loop\" scale=\"aspect\" bgcolor=\"$bgcolor\" pluginspage=\"http://apple.com/quicktime/download/\"></embed>"; } } else { $embed .= "<script type='text/javascript'>embed_quicktime('$type', '$bgcolor', '$width', '$height', '" . $this->get_link() . "', '$placeholder', '$loop');</script>"; } } // Windows Media elseif ($handler === 'wmedia') { if (is_numeric($height)) { $height += 45; } if ($native) { $embed .= "<embed type=\"application/x-mplayer2\" src=\"" . $this->get_link() . "\" autosize=\"1\" width=\"$width\" height=\"$height\" showcontrols=\"1\" showstatusbar=\"0\" showdisplay=\"0\" autostart=\"0\"></embed>"; } else { $embed .= "<script type='text/javascript'>embed_wmedia('$width', '$height', '" . $this->get_link() . "');</script>"; } } // Everything else else { $embed .= '<a href="' . $this->get_link() . '" class="' . $altclass . '">' . $alt . '</a>'; } return $embed; } /** * Get the real media type * * Often, feeds lie to us, necessitating a bit of deeper inspection. This * converts types to their canonical representations based on the file * extension * * @see get_type() * @param bool $find_handler Internal use only, use {@see get_handler()} instead * @return string|null MIME type */ public function get_real_type(bool $find_handler = false) { // Mime-types by handler. $types_flash = ['application/x-shockwave-flash', 'application/futuresplash']; // Flash $types_fmedia = ['video/flv', 'video/x-flv','flv-application/octet-stream']; // Flash Media Player $types_quicktime = ['audio/3gpp', 'audio/3gpp2', 'audio/aac', 'audio/x-aac', 'audio/aiff', 'audio/x-aiff', 'audio/mid', 'audio/midi', 'audio/x-midi', 'audio/mp4', 'audio/m4a', 'audio/x-m4a', 'audio/wav', 'audio/x-wav', 'video/3gpp', 'video/3gpp2', 'video/m4v', 'video/x-m4v', 'video/mp4', 'video/mpeg', 'video/x-mpeg', 'video/quicktime', 'video/sd-video']; // QuickTime $types_wmedia = ['application/asx', 'application/x-mplayer2', 'audio/x-ms-wma', 'audio/x-ms-wax', 'video/x-ms-asf-plugin', 'video/x-ms-asf', 'video/x-ms-wm', 'video/x-ms-wmv', 'video/x-ms-wvx']; // Windows Media $types_mp3 = ['audio/mp3', 'audio/x-mp3', 'audio/mpeg', 'audio/x-mpeg']; // MP3 $type = $this->get_type(); if ($type !== null) { $type = strtolower($type); } // If we encounter an unsupported mime-type, check the file extension and guess intelligently. if (!in_array($type, array_merge($types_flash, $types_fmedia, $types_quicktime, $types_wmedia, $types_mp3))) { $extension = $this->get_extension(); if ($extension === null) { return null; } switch (strtolower($extension)) { // Audio mime-types case 'aac': case 'adts': $type = 'audio/acc'; break; case 'aif': case 'aifc': case 'aiff': case 'cdda': $type = 'audio/aiff'; break; case 'bwf': $type = 'audio/wav'; break; case 'kar': case 'mid': case 'midi': case 'smf': $type = 'audio/midi'; break; case 'm4a': $type = 'audio/x-m4a'; break; case 'mp3': case 'swa': $type = 'audio/mp3'; break; case 'wav': $type = 'audio/wav'; break; case 'wax': $type = 'audio/x-ms-wax'; break; case 'wma': $type = 'audio/x-ms-wma'; break; case '3gp': case '3gpp': // Video mime-types $type = 'video/3gpp'; break; case '3g2': case '3gp2': $type = 'video/3gpp2'; break; case 'asf': $type = 'video/x-ms-asf'; break; case 'flv': $type = 'video/x-flv'; break; case 'm1a': case 'm1s': case 'm1v': case 'm15': case 'm75': case 'mp2': case 'mpa': case 'mpeg': case 'mpg': case 'mpm': case 'mpv': $type = 'video/mpeg'; break; case 'm4v': $type = 'video/x-m4v'; break; case 'mov': case 'qt': $type = 'video/quicktime'; break; case 'mp4': case 'mpg4': $type = 'video/mp4'; break; case 'sdv': $type = 'video/sd-video'; break; case 'wm': $type = 'video/x-ms-wm'; break; case 'wmv': $type = 'video/x-ms-wmv'; break; case 'wvx': $type = 'video/x-ms-wvx'; break; case 'spl': // Flash mime-types $type = 'application/futuresplash'; break; case 'swf': $type = 'application/x-shockwave-flash'; break; } } if ($find_handler) { if (in_array($type, $types_flash)) { return 'flash'; } elseif (in_array($type, $types_fmedia)) { return 'fmedia'; } elseif (in_array($type, $types_quicktime)) { return 'quicktime'; } elseif (in_array($type, $types_wmedia)) { return 'wmedia'; } elseif (in_array($type, $types_mp3)) { return 'mp3'; } return null; } return $type; } } class_alias('SimplePie\Enclosure', 'SimplePie_Enclosure'); ������������������������������������������������������������������������������������������������������������������Cache/File.php��������������������������������������������������������������������������������������0000644�����������������00000005641�15250524467�0007157 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php // SPDX-FileCopyrightText: 2004-2023 Ryan Parman, Sam Sneddon, Ryan McCue // SPDX-License-Identifier: BSD-3-Clause declare(strict_types=1); namespace SimplePie\Cache; /** * Caches data to the filesystem * * @deprecated since SimplePie 1.8.0, use implementation of "Psr\SimpleCache\CacheInterface" instead */ class File implements Base { /** * Location string * * @see SimplePie::$cache_location * @var string */ protected $location; /** * Filename * * @var string */ protected $filename; /** * File extension * * @var string */ protected $extension; /** * File path * * @var string */ protected $name; /** * Create a new cache object * * @param string $location Location string (from SimplePie::$cache_location) * @param string $name Unique ID for the cache * @param Base::TYPE_FEED|Base::TYPE_IMAGE $type Either TYPE_FEED for SimplePie data, or TYPE_IMAGE for image data */ public function __construct(string $location, string $name, $type) { $this->location = $location; $this->filename = $name; $this->extension = $type; $this->name = "$this->location/$this->filename.$this->extension"; } /** * Save data to the cache * * @param array<mixed>|\SimplePie\SimplePie $data Data to store in the cache. If passed a SimplePie object, only cache the $data property * @return bool Successfulness */ public function save($data) { if (file_exists($this->name) && is_writable($this->name) || file_exists($this->location) && is_writable($this->location)) { if ($data instanceof \SimplePie\SimplePie) { $data = $data->data; } $data = serialize($data); return (bool) file_put_contents($this->name, $data); } return false; } /** * Retrieve the data saved to the cache * * @return array<mixed>|false Data for SimplePie::$data */ public function load() { if (file_exists($this->name) && is_readable($this->name)) { return unserialize((string) file_get_contents($this->name)); } return false; } /** * Retrieve the last modified time for the cache * * @return int|false Timestamp */ public function mtime() { return @filemtime($this->name); } /** * Set the last modified time to the current time * * @return bool Success status */ public function touch() { return @touch($this->name); } /** * Remove the cache * * @return bool Success status */ public function unlink() { if (file_exists($this->name)) { return unlink($this->name); } return false; } } class_alias('SimplePie\Cache\File', 'SimplePie_Cache_File'); �����������������������������������������������������������������������������������������������Cache/Memcache.php����������������������������������������������������������������������������������0000644�����������������00000007200�15250524467�0007773 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php // SPDX-FileCopyrightText: 2004-2023 Ryan Parman, Sam Sneddon, Ryan McCue // SPDX-License-Identifier: BSD-3-Clause declare(strict_types=1); namespace SimplePie\Cache; use Memcache as NativeMemcache; /** * Caches data to memcache * * Registered for URLs with the "memcache" protocol * * For example, `memcache://localhost:11211/?timeout=3600&prefix=sp_` will * connect to memcache on `localhost` on port 11211. All tables will be * prefixed with `sp_` and data will expire after 3600 seconds * * @uses Memcache * @deprecated since SimplePie 1.8.0, use implementation of "Psr\SimpleCache\CacheInterface" instead */ class Memcache implements Base { /** * Memcache instance * * @var NativeMemcache */ protected $cache; /** * Options * * @var array<string, mixed> */ protected $options; /** * Cache name * * @var string */ protected $name; /** * Create a new cache object * * @param string $location Location string (from SimplePie::$cache_location) * @param string $name Unique ID for the cache * @param Base::TYPE_FEED|Base::TYPE_IMAGE $type Either TYPE_FEED for SimplePie data, or TYPE_IMAGE for image data */ public function __construct(string $location, string $name, $type) { $this->options = [ 'host' => '127.0.0.1', 'port' => 11211, 'extras' => [ 'timeout' => 3600, // one hour 'prefix' => 'simplepie_', ], ]; $this->options = array_replace_recursive($this->options, \SimplePie\Cache::parse_URL($location)); $this->name = $this->options['extras']['prefix'] . md5("$name:$type"); $this->cache = new NativeMemcache(); $this->cache->addServer($this->options['host'], (int) $this->options['port']); } /** * Save data to the cache * * @param array<mixed>|\SimplePie\SimplePie $data Data to store in the cache. If passed a SimplePie object, only cache the $data property * @return bool Successfulness */ public function save($data) { if ($data instanceof \SimplePie\SimplePie) { $data = $data->data; } return $this->cache->set($this->name, serialize($data), MEMCACHE_COMPRESSED, (int) $this->options['extras']['timeout']); } /** * Retrieve the data saved to the cache * * @return array<mixed>|false Data for SimplePie::$data */ public function load() { $data = $this->cache->get($this->name); if ($data !== false) { return unserialize($data); } return false; } /** * Retrieve the last modified time for the cache * * @return int|false Timestamp */ public function mtime() { $data = $this->cache->get($this->name); if ($data !== false) { // essentially ignore the mtime because Memcache expires on its own return time(); } return false; } /** * Set the last modified time to the current time * * @return bool Success status */ public function touch() { $data = $this->cache->get($this->name); if ($data !== false) { return $this->cache->set($this->name, $data, MEMCACHE_COMPRESSED, (int) $this->options['extras']['timeout']); } return false; } /** * Remove the cache * * @return bool Success status */ public function unlink() { return $this->cache->delete($this->name, 0); } } class_alias('SimplePie\Cache\Memcache', 'SimplePie_Cache_Memcache'); ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Cache/Memcached.php���������������������������������������������������������������������������������0000644�����������������00000007365�15250524467�0010153 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php // SPDX-FileCopyrightText: 2004-2023 Ryan Parman, Sam Sneddon, Ryan McCue // SPDX-FileCopyrightText: 2015 Paul L. McNeely // SPDX-License-Identifier: BSD-3-Clause declare(strict_types=1); namespace SimplePie\Cache; use Memcached as NativeMemcached; /** * Caches data to memcached * * Registered for URLs with the "memcached" protocol * * For example, `memcached://localhost:11211/?timeout=3600&prefix=sp_` will * connect to memcached on `localhost` on port 11211. All tables will be * prefixed with `sp_` and data will expire after 3600 seconds * * @uses Memcached * @deprecated since SimplePie 1.8.0, use implementation of "Psr\SimpleCache\CacheInterface" instead */ class Memcached implements Base { /** * NativeMemcached instance * @var NativeMemcached */ protected $cache; /** * Options * @var array<string, mixed> */ protected $options; /** * Cache name * @var string */ protected $name; /** * Create a new cache object * @param string $location Location string (from SimplePie::$cache_location) * @param string $name Unique ID for the cache * @param Base::TYPE_FEED|Base::TYPE_IMAGE $type Either TYPE_FEED for SimplePie data, or TYPE_IMAGE for image data */ public function __construct(string $location, string $name, $type) { $this->options = [ 'host' => '127.0.0.1', 'port' => 11211, 'extras' => [ 'timeout' => 3600, // one hour 'prefix' => 'simplepie_', ], ]; $this->options = array_replace_recursive($this->options, \SimplePie\Cache::parse_URL($location)); $this->name = $this->options['extras']['prefix'] . md5("$name:$type"); $this->cache = new NativeMemcached(); $this->cache->addServer($this->options['host'], (int)$this->options['port']); } /** * Save data to the cache * @param array<mixed>|\SimplePie\SimplePie $data Data to store in the cache. If passed a SimplePie object, only cache the $data property * @return bool Successfulness */ public function save($data) { if ($data instanceof \SimplePie\SimplePie) { $data = $data->data; } return $this->setData(serialize($data)); } /** * Retrieve the data saved to the cache * @return array<mixed>|false Data for SimplePie::$data */ public function load() { $data = $this->cache->get($this->name); if ($data !== false) { return unserialize($data); } return false; } /** * Retrieve the last modified time for the cache * @return int Timestamp */ public function mtime() { $data = $this->cache->get($this->name . '_mtime'); return (int) $data; } /** * Set the last modified time to the current time * @return bool Success status */ public function touch() { $data = $this->cache->get($this->name); return $this->setData($data); } /** * Remove the cache * @return bool Success status */ public function unlink() { return $this->cache->delete($this->name, 0); } /** * Set the last modified time and data to NativeMemcached * @param string|false $data * @return bool Success status */ private function setData($data): bool { if ($data !== false) { $this->cache->set($this->name . '_mtime', time(), (int)$this->options['extras']['timeout']); return $this->cache->set($this->name, $data, (int)$this->options['extras']['timeout']); } return false; } } class_alias('SimplePie\Cache\Memcached', 'SimplePie_Cache_Memcached'); ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Cache/NameFilter.php��������������������������������������������������������������������������������0000644�����������������00000002227�15250524467�0010323 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php // SPDX-FileCopyrightText: 2004-2023 Ryan Parman, Sam Sneddon, Ryan McCue // SPDX-License-Identifier: BSD-3-Clause declare(strict_types=1); namespace SimplePie\Cache; /** * Interface for creating a cache filename */ interface NameFilter { /** * Method to create cache filename with. * * The returning name MUST follow the rules for keys in PSR-16. * * @link https://www.php-fig.org/psr/psr-16/ * * The returning name MUST be a string of at least one character * that uniquely identifies a cached item, MUST only contain the * characters A-Z, a-z, 0-9, _, and . in any order in UTF-8 encoding * and MUST not longer then 64 characters. The following characters * are reserved for future extensions and MUST NOT be used: {}()/\@: * * A provided implementing library MAY support additional characters * and encodings or longer lengths, but MUST support at least that * minimum. * * @param string $name The name for the cache will be most likely an url with query string * * @return string the new cache name */ public function filter(string $name): string; } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Cache/error_log�������������������������������������������������������������������������������������0000644�����������������00000151540�15250524467�0007504 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������[19-Dec-2025 15:49:14 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\DataCache" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/BaseDataCache.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/BaseDataCache.php on line 17 [19-Dec-2025 15:49:14 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\NameFilter" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/CallableNameFilter.php:13 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/CallableNameFilter.php on line 13 [19-Dec-2025 15:49:15 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/DB.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/DB.php on line 17 [19-Dec-2025 15:49:15 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/File.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/File.php on line 15 [19-Dec-2025 15:49:15 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcache.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcache.php on line 24 [19-Dec-2025 15:49:16 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcached.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcached.php on line 25 [19-Dec-2025 15:49:16 UTC] PHP Fatal error: Uncaught Error: Class "SimplePie\Cache\DB" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/MySQL.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/MySQL.php on line 21 [19-Dec-2025 15:49:16 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\DataCache" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Psr16.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Psr16.php on line 19 [19-Dec-2025 15:49:17 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Redis.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Redis.php on line 25 [03-Apr-2026 19:13:12 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcached.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcached.php on line 25 [03-Apr-2026 19:13:13 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/File.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/File.php on line 15 [03-Apr-2026 19:13:13 UTC] PHP Fatal error: Uncaught Error: Class "SimplePie\Cache\DB" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/MySQL.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/MySQL.php on line 21 [03-Apr-2026 19:13:13 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\NameFilter" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/CallableNameFilter.php:13 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/CallableNameFilter.php on line 13 [03-Apr-2026 19:13:13 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\DataCache" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/BaseDataCache.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/BaseDataCache.php on line 17 [03-Apr-2026 19:13:14 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/DB.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/DB.php on line 17 [03-Apr-2026 19:13:14 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcache.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcache.php on line 24 [03-Apr-2026 19:13:14 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Redis.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Redis.php on line 25 [03-Apr-2026 19:13:14 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\DataCache" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Psr16.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Psr16.php on line 19 [05-Apr-2026 16:35:30 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Redis.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Redis.php on line 25 [05-Apr-2026 16:35:31 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/DB.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/DB.php on line 17 [05-Apr-2026 16:35:31 UTC] PHP Fatal error: Uncaught Error: Class "SimplePie\Cache\DB" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/MySQL.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/MySQL.php on line 21 [05-Apr-2026 16:35:31 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\DataCache" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/BaseDataCache.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/BaseDataCache.php on line 17 [05-Apr-2026 16:35:31 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcached.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcached.php on line 25 [05-Apr-2026 16:35:32 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcache.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcache.php on line 24 [05-Apr-2026 16:35:32 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\DataCache" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Psr16.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Psr16.php on line 19 [05-Apr-2026 16:35:33 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\NameFilter" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/CallableNameFilter.php:13 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/CallableNameFilter.php on line 13 [05-Apr-2026 16:35:33 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/File.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/File.php on line 15 [08-Apr-2026 14:31:47 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\NameFilter" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/CallableNameFilter.php:13 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/CallableNameFilter.php on line 13 [08-Apr-2026 14:31:47 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcached.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcached.php on line 25 [08-Apr-2026 14:31:47 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Redis.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Redis.php on line 25 [08-Apr-2026 14:31:47 UTC] PHP Fatal error: Uncaught Error: Class "SimplePie\Cache\DB" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/MySQL.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/MySQL.php on line 21 [08-Apr-2026 14:31:48 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/DB.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/DB.php on line 17 [08-Apr-2026 14:31:48 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\DataCache" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Psr16.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Psr16.php on line 19 [08-Apr-2026 14:31:48 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\DataCache" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/BaseDataCache.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/BaseDataCache.php on line 17 [08-Apr-2026 14:31:49 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcache.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcache.php on line 24 [08-Apr-2026 14:31:49 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/File.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/File.php on line 15 [15-Jul-2026 16:18:30 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\DataCache" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Psr16.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Psr16.php on line 19 [15-Jul-2026 16:18:30 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/DB.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/DB.php on line 17 [15-Jul-2026 16:18:30 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcache.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcache.php on line 24 [15-Jul-2026 16:18:30 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\DataCache" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/BaseDataCache.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/BaseDataCache.php on line 17 [15-Jul-2026 16:18:31 UTC] PHP Fatal error: Uncaught Error: Class "SimplePie\Cache\DB" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/MySQL.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/MySQL.php on line 21 [15-Jul-2026 16:18:31 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Redis.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Redis.php on line 25 [15-Jul-2026 16:18:31 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/File.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/File.php on line 15 [15-Jul-2026 16:18:31 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\NameFilter" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/CallableNameFilter.php:13 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/CallableNameFilter.php on line 13 [15-Jul-2026 16:18:32 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcached.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcached.php on line 25 [24-Jul-2026 12:14:24 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/File.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/File.php on line 15 [24-Jul-2026 12:14:24 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/DB.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/DB.php on line 17 [24-Jul-2026 12:14:24 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcached.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcached.php on line 25 [24-Jul-2026 12:14:25 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\DataCache" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/BaseDataCache.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/BaseDataCache.php on line 17 [24-Jul-2026 12:14:25 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcache.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcache.php on line 24 [24-Jul-2026 12:14:25 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\DataCache" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Psr16.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Psr16.php on line 19 [24-Jul-2026 12:14:25 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\NameFilter" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/CallableNameFilter.php:13 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/CallableNameFilter.php on line 13 [24-Jul-2026 12:14:26 UTC] PHP Fatal error: Uncaught Error: Class "SimplePie\Cache\DB" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/MySQL.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/MySQL.php on line 21 [24-Jul-2026 12:14:26 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Redis.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Redis.php on line 25 [25-Jul-2026 05:34:45 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\NameFilter" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/CallableNameFilter.php:13 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/CallableNameFilter.php on line 13 [25-Jul-2026 05:34:45 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/DB.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/DB.php on line 17 [25-Jul-2026 05:34:46 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/File.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/File.php on line 15 [25-Jul-2026 05:34:46 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Redis.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Redis.php on line 25 [25-Jul-2026 05:34:47 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\DataCache" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/BaseDataCache.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/BaseDataCache.php on line 17 [25-Jul-2026 05:34:47 UTC] PHP Fatal error: Uncaught Error: Class "SimplePie\Cache\DB" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/MySQL.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/MySQL.php on line 21 [25-Jul-2026 05:34:48 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcached.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcached.php on line 25 [25-Jul-2026 05:34:48 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\DataCache" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Psr16.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Psr16.php on line 19 [25-Jul-2026 05:34:49 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcache.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcache.php on line 24 [04-Aug-2026 23:31:24 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/File.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/File.php on line 15 [04-Aug-2026 23:31:25 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\DataCache" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/BaseDataCache.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/BaseDataCache.php on line 17 [04-Aug-2026 23:31:25 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcache.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcache.php on line 24 [04-Aug-2026 23:31:26 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcached.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcached.php on line 25 [04-Aug-2026 23:31:26 UTC] PHP Fatal error: Uncaught Error: Class "SimplePie\Cache\DB" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/MySQL.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/MySQL.php on line 21 [04-Aug-2026 23:31:27 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Redis.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Redis.php on line 25 [04-Aug-2026 23:31:27 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/DB.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/DB.php on line 17 [04-Aug-2026 23:31:28 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\NameFilter" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/CallableNameFilter.php:13 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/CallableNameFilter.php on line 13 [04-Aug-2026 23:31:29 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\DataCache" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Psr16.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Psr16.php on line 19 [13-Aug-2026 01:30:28 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcached.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcached.php on line 25 [13-Aug-2026 01:30:28 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\NameFilter" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/CallableNameFilter.php:13 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/CallableNameFilter.php on line 13 [13-Aug-2026 01:30:28 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/DB.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/DB.php on line 17 [13-Aug-2026 01:30:29 UTC] PHP Fatal error: Uncaught Error: Class "SimplePie\Cache\DB" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/MySQL.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/MySQL.php on line 21 [13-Aug-2026 01:30:29 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\DataCache" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/BaseDataCache.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/BaseDataCache.php on line 17 [13-Aug-2026 01:30:29 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/File.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/File.php on line 15 [13-Aug-2026 01:30:29 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcache.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcache.php on line 24 [13-Aug-2026 01:30:30 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Redis.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Redis.php on line 25 [13-Aug-2026 01:30:30 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\DataCache" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Psr16.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Psr16.php on line 19 [13-Aug-2026 12:58:31 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\DataCache" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/BaseDataCache.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/BaseDataCache.php on line 17 [13-Aug-2026 12:58:31 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\DataCache" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Psr16.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Psr16.php on line 19 [13-Aug-2026 12:58:31 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcache.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcache.php on line 24 [13-Aug-2026 12:58:32 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/DB.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/DB.php on line 17 [13-Aug-2026 12:58:32 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/File.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/File.php on line 15 [13-Aug-2026 12:58:33 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Redis.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Redis.php on line 25 [13-Aug-2026 12:58:33 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\NameFilter" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/CallableNameFilter.php:13 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/CallableNameFilter.php on line 13 [13-Aug-2026 12:58:33 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcached.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcached.php on line 25 [13-Aug-2026 12:58:34 UTC] PHP Fatal error: Uncaught Error: Class "SimplePie\Cache\DB" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/MySQL.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/MySQL.php on line 21 [19-Aug-2026 21:32:12 UTC] PHP Fatal error: Uncaught Error: Class "SimplePie\Cache\DB" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/MySQL.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/MySQL.php on line 21 [19-Aug-2026 21:32:12 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\DataCache" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/BaseDataCache.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/BaseDataCache.php on line 17 [19-Aug-2026 21:32:12 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\DataCache" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Psr16.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Psr16.php on line 19 [19-Aug-2026 21:32:12 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\NameFilter" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/CallableNameFilter.php:13 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/CallableNameFilter.php on line 13 [19-Aug-2026 21:32:12 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Redis.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Redis.php on line 25 [19-Aug-2026 21:32:12 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/File.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/File.php on line 15 [19-Aug-2026 21:32:12 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcache.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcache.php on line 24 [19-Aug-2026 21:32:12 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/DB.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/DB.php on line 17 [19-Aug-2026 21:32:12 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcached.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcached.php on line 25 [19-Aug-2026 22:06:59 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\DataCache" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Psr16.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Psr16.php on line 19 [19-Aug-2026 22:06:59 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\DataCache" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/BaseDataCache.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/BaseDataCache.php on line 17 [19-Aug-2026 22:06:59 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\NameFilter" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/CallableNameFilter.php:13 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/CallableNameFilter.php on line 13 [19-Aug-2026 22:07:00 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Redis.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Redis.php on line 25 [19-Aug-2026 22:07:00 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/DB.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/DB.php on line 17 [19-Aug-2026 22:07:00 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcache.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcache.php on line 24 [19-Aug-2026 22:07:00 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcached.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcached.php on line 25 [19-Aug-2026 22:07:01 UTC] PHP Fatal error: Uncaught Error: Class "SimplePie\Cache\DB" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/MySQL.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/MySQL.php on line 21 [19-Aug-2026 22:07:01 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/File.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/File.php on line 15 [19-Aug-2026 23:27:19 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcache.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcache.php on line 24 [19-Aug-2026 23:27:19 UTC] PHP Fatal error: Uncaught Error: Class "SimplePie\Cache\DB" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/MySQL.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/MySQL.php on line 21 [19-Aug-2026 23:27:20 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\NameFilter" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/CallableNameFilter.php:13 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/CallableNameFilter.php on line 13 [19-Aug-2026 23:27:21 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\DataCache" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Psr16.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Psr16.php on line 19 [19-Aug-2026 23:27:21 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\DataCache" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/BaseDataCache.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/BaseDataCache.php on line 17 [19-Aug-2026 23:27:21 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/DB.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/DB.php on line 17 [19-Aug-2026 23:27:21 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcached.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcached.php on line 25 [19-Aug-2026 23:27:22 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Redis.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Redis.php on line 25 [19-Aug-2026 23:27:22 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/File.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/File.php on line 15 [03-Sep-2026 06:32:00 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcache.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcache.php on line 24 [03-Sep-2026 06:32:02 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\DataCache" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Psr16.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Psr16.php on line 19 [03-Sep-2026 06:32:03 UTC] PHP Fatal error: Uncaught Error: Class "SimplePie\Cache\DB" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/MySQL.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/MySQL.php on line 21 [03-Sep-2026 06:32:04 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\NameFilter" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/CallableNameFilter.php:13 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/CallableNameFilter.php on line 13 [03-Sep-2026 06:33:14 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Redis.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Redis.php on line 25 [03-Sep-2026 06:33:18 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/File.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/File.php on line 15 [03-Sep-2026 06:46:14 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/DB.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/DB.php on line 17 [03-Sep-2026 06:46:15 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcached.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcached.php on line 25 [03-Sep-2026 06:46:23 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\DataCache" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/BaseDataCache.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/BaseDataCache.php on line 17 [03-Sep-2026 08:08:14 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\DataCache" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/BaseDataCache.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/BaseDataCache.php on line 17 [03-Sep-2026 08:08:15 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\NameFilter" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/CallableNameFilter.php:13 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/CallableNameFilter.php on line 13 [03-Sep-2026 08:08:16 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/DB.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/DB.php on line 17 [03-Sep-2026 08:08:17 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/File.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/File.php on line 15 [03-Sep-2026 08:08:17 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcache.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcache.php on line 24 [03-Sep-2026 08:08:18 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcached.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcached.php on line 25 [03-Sep-2026 08:08:19 UTC] PHP Fatal error: Uncaught Error: Class "SimplePie\Cache\DB" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/MySQL.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/MySQL.php on line 21 [03-Sep-2026 08:08:21 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\DataCache" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Psr16.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Psr16.php on line 19 [03-Sep-2026 08:08:21 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Redis.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Redis.php on line 25 [03-Sep-2026 21:23:09 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\DataCache" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/BaseDataCache.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/BaseDataCache.php on line 17 [03-Sep-2026 21:23:09 UTC] PHP Fatal error: Uncaught Error: Class "SimplePie\Cache\DB" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/MySQL.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/MySQL.php on line 21 [03-Sep-2026 21:23:09 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/File.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/File.php on line 15 [03-Sep-2026 21:23:10 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\NameFilter" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/CallableNameFilter.php:13 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/CallableNameFilter.php on line 13 [03-Sep-2026 23:30:29 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcache.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcache.php on line 24 [03-Sep-2026 23:30:30 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/DB.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/DB.php on line 17 [03-Sep-2026 23:30:31 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Redis.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Redis.php on line 25 [03-Sep-2026 23:30:31 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcached.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcached.php on line 25 [04-Sep-2026 02:19:05 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\DataCache" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Psr16.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Psr16.php on line 19 [04-Sep-2026 06:36:52 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\DataCache" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/BaseDataCache.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/BaseDataCache.php on line 17 [04-Sep-2026 06:36:52 UTC] PHP Fatal error: Uncaught Error: Class "SimplePie\Cache\DB" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/MySQL.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/MySQL.php on line 21 [04-Sep-2026 06:36:53 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/File.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/File.php on line 15 [04-Sep-2026 06:36:53 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\NameFilter" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/CallableNameFilter.php:13 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/CallableNameFilter.php on line 13 [04-Sep-2026 06:37:14 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\DataCache" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Psr16.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Psr16.php on line 19 [04-Sep-2026 08:33:55 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/DB.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/DB.php on line 17 [04-Sep-2026 08:33:55 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcached.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcached.php on line 25 [04-Sep-2026 08:33:56 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Redis.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Redis.php on line 25 [04-Sep-2026 11:18:13 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcache.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcache.php on line 24 [04-Sep-2026 11:44:46 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\DataCache" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/BaseDataCache.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/BaseDataCache.php on line 17 [04-Sep-2026 11:44:46 UTC] PHP Fatal error: Uncaught Error: Class "SimplePie\Cache\DB" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/MySQL.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/MySQL.php on line 21 [04-Sep-2026 11:44:47 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/File.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/File.php on line 15 [04-Sep-2026 11:44:48 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\NameFilter" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/CallableNameFilter.php:13 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/CallableNameFilter.php on line 13 [04-Sep-2026 11:44:49 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcache.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcache.php on line 24 [04-Sep-2026 11:44:50 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/DB.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/DB.php on line 17 [04-Sep-2026 11:44:50 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Redis.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Redis.php on line 25 [04-Sep-2026 11:44:51 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcached.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcached.php on line 25 [04-Sep-2026 11:44:52 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\DataCache" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Psr16.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Psr16.php on line 19 [05-Sep-2026 01:12:35 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\DataCache" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/BaseDataCache.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/BaseDataCache.php on line 17 [05-Sep-2026 01:12:36 UTC] PHP Fatal error: Uncaught Error: Class "SimplePie\Cache\DB" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/MySQL.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/MySQL.php on line 21 [05-Sep-2026 01:12:36 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/File.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/File.php on line 15 [05-Sep-2026 01:12:37 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\NameFilter" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/CallableNameFilter.php:13 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/CallableNameFilter.php on line 13 [05-Sep-2026 07:01:32 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcache.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcache.php on line 24 [05-Sep-2026 07:01:33 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/DB.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/DB.php on line 17 [05-Sep-2026 07:01:34 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Redis.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Redis.php on line 25 [05-Sep-2026 07:01:36 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\Base" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcached.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Memcached.php on line 25 [05-Sep-2026 08:00:45 UTC] PHP Fatal error: Uncaught Error: Interface "SimplePie\Cache\DataCache" not found in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Psr16.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/SimplePie/src/Cache/Psr16.php on line 19 ����������������������������������������������������������������������������������������������������������������������������������������������������������������Cache/MySQL.php�������������������������������������������������������������������������������������0000644�����������������00000032607�15250524467�0007247 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php // SPDX-FileCopyrightText: 2004-2023 Ryan Parman, Sam Sneddon, Ryan McCue // SPDX-License-Identifier: BSD-3-Clause declare(strict_types=1); namespace SimplePie\Cache; /** * Caches data to a MySQL database * * Registered for URLs with the "mysql" protocol * * For example, `mysql://root:password@localhost:3306/mydb?prefix=sp_` will * connect to the `mydb` database on `localhost` on port 3306, with the user * `root` and the password `password`. All tables will be prefixed with `sp_` * * @deprecated since SimplePie 1.8.0, use implementation of "Psr\SimpleCache\CacheInterface" instead */ class MySQL extends DB { /** * PDO instance * * @var \PDO|null */ protected $mysql; /** * Options * * @var array<string, mixed> */ protected $options; /** * Cache ID * * @var string */ protected $id; /** * Create a new cache object * * @param string $location Location string (from SimplePie::$cache_location) * @param string $name Unique ID for the cache * @param Base::TYPE_FEED|Base::TYPE_IMAGE $type Either TYPE_FEED for SimplePie data, or TYPE_IMAGE for image data */ public function __construct(string $location, string $name, $type) { $this->options = [ 'user' => null, 'pass' => null, 'host' => '127.0.0.1', 'port' => '3306', 'path' => '', 'extras' => [ 'prefix' => '', 'cache_purge_time' => 2592000 ], ]; $this->options = array_replace_recursive($this->options, \SimplePie\Cache::parse_URL($location)); // Path is prefixed with a "/" $this->options['dbname'] = substr($this->options['path'], 1); try { $this->mysql = new \PDO("mysql:dbname={$this->options['dbname']};host={$this->options['host']};port={$this->options['port']}", $this->options['user'], $this->options['pass'], [\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8']); } catch (\PDOException $e) { $this->mysql = null; return; } $this->id = $name . $type; if (!$query = $this->mysql->query('SHOW TABLES')) { $this->mysql = null; return; } $db = []; while ($row = $query->fetchColumn()) { $db[] = $row; } if (!in_array($this->options['extras']['prefix'] . 'cache_data', $db)) { $query = $this->mysql->exec('CREATE TABLE `' . $this->options['extras']['prefix'] . 'cache_data` (`id` TEXT CHARACTER SET utf8 NOT NULL, `items` SMALLINT NOT NULL DEFAULT 0, `data` BLOB NOT NULL, `mtime` INT UNSIGNED NOT NULL, UNIQUE (`id`(125)))'); if ($query === false) { trigger_error("Can't create " . $this->options['extras']['prefix'] . "cache_data table, check permissions", \E_USER_WARNING); $this->mysql = null; return; } } if (!in_array($this->options['extras']['prefix'] . 'items', $db)) { $query = $this->mysql->exec('CREATE TABLE `' . $this->options['extras']['prefix'] . 'items` (`feed_id` TEXT CHARACTER SET utf8 NOT NULL, `id` TEXT CHARACTER SET utf8 NOT NULL, `data` MEDIUMBLOB NOT NULL, `posted` INT UNSIGNED NOT NULL, INDEX `feed_id` (`feed_id`(125)))'); if ($query === false) { trigger_error("Can't create " . $this->options['extras']['prefix'] . "items table, check permissions", \E_USER_WARNING); $this->mysql = null; return; } } } /** * Save data to the cache * * @param array<string>|\SimplePie\SimplePie $data Data to store in the cache. If passed a SimplePie object, only cache the $data property * @return bool Successfulness */ public function save($data) { if ($this->mysql === null) { return false; } $query = $this->mysql->prepare('DELETE i, cd FROM `' . $this->options['extras']['prefix'] . 'cache_data` cd, ' . '`' . $this->options['extras']['prefix'] . 'items` i ' . 'WHERE cd.id = i.feed_id ' . 'AND cd.mtime < (unix_timestamp() - :purge_time)'); $query->bindValue(':purge_time', $this->options['extras']['cache_purge_time']); if (!$query->execute()) { return false; } if ($data instanceof \SimplePie\SimplePie) { $data = clone $data; $prepared = self::prepare_simplepie_object_for_cache($data); $query = $this->mysql->prepare('SELECT COUNT(*) FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :feed'); $query->bindValue(':feed', $this->id); if ($query->execute()) { if ($query->fetchColumn() > 0) { $items = count($prepared[1]); if ($items) { $sql = 'UPDATE `' . $this->options['extras']['prefix'] . 'cache_data` SET `items` = :items, `data` = :data, `mtime` = :time WHERE `id` = :feed'; $query = $this->mysql->prepare($sql); $query->bindValue(':items', $items); } else { $sql = 'UPDATE `' . $this->options['extras']['prefix'] . 'cache_data` SET `data` = :data, `mtime` = :time WHERE `id` = :feed'; $query = $this->mysql->prepare($sql); } $query->bindValue(':data', $prepared[0]); $query->bindValue(':time', time()); $query->bindValue(':feed', $this->id); if (!$query->execute()) { return false; } } else { $query = $this->mysql->prepare('INSERT INTO `' . $this->options['extras']['prefix'] . 'cache_data` (`id`, `items`, `data`, `mtime`) VALUES(:feed, :count, :data, :time)'); $query->bindValue(':feed', $this->id); $query->bindValue(':count', count($prepared[1])); $query->bindValue(':data', $prepared[0]); $query->bindValue(':time', time()); if (!$query->execute()) { return false; } } $ids = array_keys($prepared[1]); if (!empty($ids)) { foreach ($ids as $id) { $database_ids[] = $this->mysql->quote($id); } $query = $this->mysql->prepare('SELECT `id` FROM `' . $this->options['extras']['prefix'] . 'items` WHERE `id` = ' . implode(' OR `id` = ', $database_ids) . ' AND `feed_id` = :feed'); $query->bindValue(':feed', $this->id); if ($query->execute()) { $existing_ids = []; while ($row = $query->fetchColumn()) { $existing_ids[] = $row; } $new_ids = array_diff($ids, $existing_ids); foreach ($new_ids as $new_id) { if (!($date = $prepared[1][$new_id]->get_date('U'))) { $date = time(); } $query = $this->mysql->prepare('INSERT INTO `' . $this->options['extras']['prefix'] . 'items` (`feed_id`, `id`, `data`, `posted`) VALUES(:feed, :id, :data, :date)'); $query->bindValue(':feed', $this->id); $query->bindValue(':id', $new_id); $query->bindValue(':data', serialize($prepared[1][$new_id]->data)); $query->bindValue(':date', $date); if (!$query->execute()) { return false; } } return true; } } else { return true; } } } else { $query = $this->mysql->prepare('SELECT `id` FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :feed'); $query->bindValue(':feed', $this->id); if ($query->execute()) { if ($query->rowCount() > 0) { $query = $this->mysql->prepare('UPDATE `' . $this->options['extras']['prefix'] . 'cache_data` SET `items` = 0, `data` = :data, `mtime` = :time WHERE `id` = :feed'); $query->bindValue(':data', serialize($data)); $query->bindValue(':time', time()); $query->bindValue(':feed', $this->id); if ($query->execute()) { return true; } } else { $query = $this->mysql->prepare('INSERT INTO `' . $this->options['extras']['prefix'] . 'cache_data` (`id`, `items`, `data`, `mtime`) VALUES(:id, 0, :data, :time)'); $query->bindValue(':id', $this->id); $query->bindValue(':data', serialize($data)); $query->bindValue(':time', time()); if ($query->execute()) { return true; } } } } return false; } /** * Retrieve the data saved to the cache * * @return array<string>|false Data for SimplePie::$data */ public function load() { if ($this->mysql === null) { return false; } $query = $this->mysql->prepare('SELECT `items`, `data` FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :id'); $query->bindValue(':id', $this->id); if ($query->execute() && ($row = $query->fetch())) { $data = unserialize($row[1]); if (isset($this->options['items'][0])) { $items = (int) $this->options['items'][0]; } else { $items = (int) $row[0]; } if ($items !== 0) { if (isset($data['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['feed'][0])) { $feed = &$data['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['feed'][0]; } elseif (isset($data['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['feed'][0])) { $feed = &$data['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['feed'][0]; } elseif (isset($data['child'][\SimplePie\SimplePie::NAMESPACE_RDF]['RDF'][0])) { $feed = &$data['child'][\SimplePie\SimplePie::NAMESPACE_RDF]['RDF'][0]; } elseif (isset($data['child'][\SimplePie\SimplePie::NAMESPACE_RSS_20]['rss'][0])) { $feed = &$data['child'][\SimplePie\SimplePie::NAMESPACE_RSS_20]['rss'][0]; } else { $feed = null; } if ($feed !== null) { $sql = 'SELECT `data` FROM `' . $this->options['extras']['prefix'] . 'items` WHERE `feed_id` = :feed ORDER BY `posted` DESC'; if ($items > 0) { $sql .= ' LIMIT ' . $items; } $query = $this->mysql->prepare($sql); $query->bindValue(':feed', $this->id); if ($query->execute()) { while ($row = $query->fetchColumn()) { $feed['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['entry'][] = unserialize((string) $row); } } else { return false; } } } return $data; } return false; } /** * Retrieve the last modified time for the cache * * @return int|false Timestamp */ public function mtime() { if ($this->mysql === null) { return false; } $query = $this->mysql->prepare('SELECT `mtime` FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :id'); $query->bindValue(':id', $this->id); if ($query->execute() && ($time = $query->fetchColumn())) { return (int) $time; } return false; } /** * Set the last modified time to the current time * * @return bool Success status */ public function touch() { if ($this->mysql === null) { return false; } $query = $this->mysql->prepare('UPDATE `' . $this->options['extras']['prefix'] . 'cache_data` SET `mtime` = :time WHERE `id` = :id'); $query->bindValue(':time', time()); $query->bindValue(':id', $this->id); return $query->execute() && $query->rowCount() > 0; } /** * Remove the cache * * @return bool Success status */ public function unlink() { if ($this->mysql === null) { return false; } $query = $this->mysql->prepare('DELETE FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :id'); $query->bindValue(':id', $this->id); $query2 = $this->mysql->prepare('DELETE FROM `' . $this->options['extras']['prefix'] . 'items` WHERE `feed_id` = :id'); $query2->bindValue(':id', $this->id); return $query->execute() && $query2->execute(); } } class_alias('SimplePie\Cache\MySQL', 'SimplePie_Cache_MySQL'); �������������������������������������������������������������������������������������������������������������������������Cache/Base.php��������������������������������������������������������������������������������������0000644�����������������00000003526�15250524467�0007152 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php // SPDX-FileCopyrightText: 2004-2023 Ryan Parman, Sam Sneddon, Ryan McCue // SPDX-License-Identifier: BSD-3-Clause declare(strict_types=1); namespace SimplePie\Cache; /** * Base for cache objects * * Classes to be used with {@see \SimplePie\Cache::register()} are expected * to implement this interface. * * @deprecated since SimplePie 1.8.0, use "Psr\SimpleCache\CacheInterface" instead */ interface Base { /** * Feed cache type * * @var string */ public const TYPE_FEED = 'spc'; /** * Image cache type * * @var string */ public const TYPE_IMAGE = 'spi'; /** * Create a new cache object * * @param string $location Location string (from SimplePie::$cache_location) * @param string $name Unique ID for the cache * @param Base::TYPE_FEED|Base::TYPE_IMAGE $type Either TYPE_FEED for SimplePie data, or TYPE_IMAGE for image data */ public function __construct(string $location, string $name, $type); /** * Save data to the cache * * @param array<mixed>|\SimplePie\SimplePie $data Data to store in the cache. If passed a SimplePie object, only cache the $data property * @return bool Successfulness */ public function save($data); /** * Retrieve the data saved to the cache * * @return array<mixed> Data for SimplePie::$data */ public function load(); /** * Retrieve the last modified time for the cache * * @return int Timestamp */ public function mtime(); /** * Set the last modified time to the current time * * @return bool Success status */ public function touch(); /** * Remove the cache * * @return bool Success status */ public function unlink(); } class_alias('SimplePie\Cache\Base', 'SimplePie_Cache_Base'); ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������Cache/Redis.php�������������������������������������������������������������������������������������0000644�����������������00000010321�15250524467�0007335 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php // SPDX-FileCopyrightText: 2004-2023 Ryan Parman, Sam Sneddon, Ryan McCue // SPDX-FileCopyrightText: 2015 Jan Kozak <galvani78@gmail.com> // SPDX-License-Identifier: BSD-3-Clause declare(strict_types=1); namespace SimplePie\Cache; use Redis as NativeRedis; /** * Caches data to redis * * Registered for URLs with the "redis" protocol * * For example, `redis://localhost:6379/?timeout=3600&prefix=sp_&dbIndex=0` will * connect to redis on `localhost` on port 6379. All tables will be * prefixed with `simple_primary-` and data will expire after 3600 seconds * * @uses Redis * @deprecated since SimplePie 1.8.0, use implementation of "Psr\SimpleCache\CacheInterface" instead */ class Redis implements Base { /** * Redis instance * * @var NativeRedis */ protected $cache; /** * Options * * @var array<string, mixed> */ protected $options; /** * Cache name * * @var string */ protected $name; /** * Create a new cache object * * @param string $location Location string (from SimplePie::$cache_location) * @param string $name Unique ID for the cache * @param Base::TYPE_FEED|Base::TYPE_IMAGE|array<string, mixed>|null $options Either TYPE_FEED for SimplePie data, or TYPE_IMAGE for image data */ public function __construct(string $location, string $name, $options = null) { //$this->cache = \flow\simple\cache\Redis::getRedisClientInstance(); $parsed = \SimplePie\Cache::parse_URL($location); $redis = new NativeRedis(); $redis->connect($parsed['host'], $parsed['port']); if (isset($parsed['pass'])) { $redis->auth($parsed['pass']); } if (isset($parsed['path'])) { $redis->select((int)substr($parsed['path'], 1)); } $this->cache = $redis; if (!is_null($options) && is_array($options)) { $this->options = $options; } else { $this->options = [ 'prefix' => 'rss:simple_primary:', 'expire' => 0, ]; } $this->name = $this->options['prefix'] . $name; } /** * @param NativeRedis $cache * @return void */ public function setRedisClient(NativeRedis $cache) { $this->cache = $cache; } /** * Save data to the cache * * @param array<mixed>|\SimplePie\SimplePie $data Data to store in the cache. If passed a SimplePie object, only cache the $data property * @return bool Successfulness */ public function save($data) { if ($data instanceof \SimplePie\SimplePie) { $data = $data->data; } $response = $this->cache->set($this->name, serialize($data)); if ($this->options['expire']) { $this->cache->expire($this->name, $this->options['expire']); } return $response; } /** * Retrieve the data saved to the cache * * @return array<mixed>|false Data for SimplePie::$data */ public function load() { $data = $this->cache->get($this->name); if ($data !== false) { return unserialize($data); } return false; } /** * Retrieve the last modified time for the cache * * @return int|false Timestamp */ public function mtime() { $data = $this->cache->get($this->name); if ($data !== false) { return time(); } return false; } /** * Set the last modified time to the current time * * @return bool Success status */ public function touch() { $data = $this->cache->get($this->name); if ($data !== false) { $return = $this->cache->set($this->name, $data); if ($this->options['expire']) { return $this->cache->expire($this->name, $this->options['expire']); } return $return; } return false; } /** * Remove the cache * * @return bool Success status */ public function unlink() { return $this->cache->set($this->name, null); } } class_alias('SimplePie\Cache\Redis', 'SimplePie_Cache_Redis'); ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Cache/CallableNameFilter.php������������������������������������������������������������������������0000644�����������������00000002756�15250524467�0011752 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php // SPDX-FileCopyrightText: 2004-2023 Ryan Parman, Sam Sneddon, Ryan McCue // SPDX-License-Identifier: BSD-3-Clause declare(strict_types=1); namespace SimplePie\Cache; /** * Creating a cache filename with callables */ final class CallableNameFilter implements NameFilter { /** * @var callable(string): string */ private $callable; /** * @param callable(string): string $callable */ public function __construct(callable $callable) { $this->callable = $callable; } /** * Method to create cache filename with. * * The returning name MUST follow the rules for keys in PSR-16. * * @link https://www.php-fig.org/psr/psr-16/ * * The returning name MUST be a string of at least one character * that uniquely identifies a cached item, MUST only contain the * characters A-Z, a-z, 0-9, _, and . in any order in UTF-8 encoding * and MUST not longer then 64 characters. The following characters * are reserved for future extensions and MUST NOT be used: {}()/\@: * * A provided implementing library MAY support additional characters * and encodings or longer lengths, but MUST support at least that * minimum. * * @param string $name The name for the cache will be most likely an url with query string * * @return string the new cache name */ public function filter(string $name): string { return call_user_func($this->callable, $name); } } ������������������Cache/DB.php����������������������������������������������������������������������������������������0000644�����������������00000007171�15250524467�0006565 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php // SPDX-FileCopyrightText: 2004-2023 Ryan Parman, Sam Sneddon, Ryan McCue // SPDX-License-Identifier: BSD-3-Clause declare(strict_types=1); namespace SimplePie\Cache; use SimplePie\Item; /** * Base class for database-based caches * * @deprecated since SimplePie 1.8.0, use implementation of "Psr\SimpleCache\CacheInterface" instead */ abstract class DB implements Base { /** * Helper for database conversion * * Converts a given {@see SimplePie} object into data to be stored * * @param \SimplePie\SimplePie $data * @return array{string, array<string, Item>} First item is the serialized data for storage, second item is the unique ID for this item */ protected static function prepare_simplepie_object_for_cache(\SimplePie\SimplePie $data) { $items = $data->get_items(); $items_by_id = []; if (!empty($items)) { foreach ($items as $item) { $items_by_id[$item->get_id()] = $item; } if (count($items_by_id) !== count($items)) { $items_by_id = []; foreach ($items as $item) { $items_by_id[$item->get_id(true)] = $item; } } if (isset($data->data['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['feed'][0])) { $channel = &$data->data['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['feed'][0]; } elseif (isset($data->data['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['feed'][0])) { $channel = &$data->data['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['feed'][0]; } elseif (isset($data->data['child'][\SimplePie\SimplePie::NAMESPACE_RDF]['RDF'][0])) { $channel = &$data->data['child'][\SimplePie\SimplePie::NAMESPACE_RDF]['RDF'][0]; } elseif (isset($data->data['child'][\SimplePie\SimplePie::NAMESPACE_RSS_20]['rss'][0]['child'][\SimplePie\SimplePie::NAMESPACE_RSS_20]['channel'][0])) { $channel = &$data->data['child'][\SimplePie\SimplePie::NAMESPACE_RSS_20]['rss'][0]['child'][\SimplePie\SimplePie::NAMESPACE_RSS_20]['channel'][0]; } else { $channel = null; } if ($channel !== null) { if (isset($channel['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['entry'])) { unset($channel['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_10]['entry']); } if (isset($channel['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['entry'])) { unset($channel['child'][\SimplePie\SimplePie::NAMESPACE_ATOM_03]['entry']); } if (isset($channel['child'][\SimplePie\SimplePie::NAMESPACE_RSS_10]['item'])) { unset($channel['child'][\SimplePie\SimplePie::NAMESPACE_RSS_10]['item']); } if (isset($channel['child'][\SimplePie\SimplePie::NAMESPACE_RSS_090]['item'])) { unset($channel['child'][\SimplePie\SimplePie::NAMESPACE_RSS_090]['item']); } if (isset($channel['child'][\SimplePie\SimplePie::NAMESPACE_RSS_20]['item'])) { unset($channel['child'][\SimplePie\SimplePie::NAMESPACE_RSS_20]['item']); } } if (isset($data->data['items'])) { unset($data->data['items']); } if (isset($data->data['ordered_items'])) { unset($data->data['ordered_items']); } } return [serialize($data->data), $items_by_id]; } } class_alias('SimplePie\Cache\DB', 'SimplePie_Cache_DB'); �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Cache/DataCache.php���������������������������������������������������������������������������������0000644�����������������00000005357�15250524467�0010101 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php // SPDX-FileCopyrightText: 2004-2023 Ryan Parman, Sam Sneddon, Ryan McCue // SPDX-License-Identifier: BSD-3-Clause declare(strict_types=1); namespace SimplePie\Cache; use InvalidArgumentException; /** * Subset of PSR-16 Cache client for caching data arrays * * Only get(), set() and delete() methods are used, * but not has(), getMultiple(), setMultiple() or deleteMultiple(). * * The methods names must be different, but should be compatible to the * methods of \Psr\SimpleCache\CacheInterface. * * @internal */ interface DataCache { /** * Fetches a value from the cache. * * Equivalent to \Psr\SimpleCache\CacheInterface::get() * <code> * public function get(string $key, mixed $default = null): mixed; * </code> * * @param string $key The unique key of this item in the cache. * @param mixed $default Default value to return if the key does not exist. * * @return array|mixed The value of the item from the cache, or $default in case of cache miss. * * @throws InvalidArgumentException * MUST be thrown if the $key string is not a legal value. */ public function get_data(string $key, $default = null); /** * Persists data in the cache, uniquely referenced by a key with an optional expiration TTL time. * * Equivalent to \Psr\SimpleCache\CacheInterface::set() * <code> * public function set(string $key, mixed $value, null|int|\DateInterval $ttl = null): bool; * </code> * * @param string $key The key of the item to store. * @param array<mixed> $value The value of the item to store, must be serializable. * @param null|int $ttl Optional. The TTL value of this item. If no value is sent and * the driver supports TTL then the library may set a default value * for it or let the driver take care of that. * * @return bool True on success and false on failure. * * @throws InvalidArgumentException * MUST be thrown if the $key string is not a legal value. */ public function set_data(string $key, array $value, ?int $ttl = null): bool; /** * Delete an item from the cache by its unique key. * * Equivalent to \Psr\SimpleCache\CacheInterface::delete() * <code> * public function delete(string $key): bool; * </code> * * @param string $key The unique cache key of the item to delete. * * @return bool True if the item was successfully removed. False if there was an error. * * @throws InvalidArgumentException * MUST be thrown if the $key string is not a legal value. */ public function delete_data(string $key): bool; } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Cache/BaseDataCache.php�����������������������������������������������������������������������������0000644�����������������00000007003�15250524467�0010662 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php // SPDX-FileCopyrightText: 2004-2023 Ryan Parman, Sam Sneddon, Ryan McCue // SPDX-License-Identifier: BSD-3-Clause declare(strict_types=1); namespace SimplePie\Cache; use InvalidArgumentException; /** * Adapter for deprecated \SimplePie\Cache\Base implementations * * @internal */ final class BaseDataCache implements DataCache { /** * @var Base */ private $cache; public function __construct(Base $cache) { $this->cache = $cache; } /** * Fetches a value from the cache. * * Equivalent to \Psr\SimpleCache\CacheInterface::get() * <code> * public function get(string $key, mixed $default = null): mixed; * </code> * * @param string $key The unique key of this item in the cache. * @param mixed $default Default value to return if the key does not exist. * * @return array|mixed The value of the item from the cache, or $default in case of cache miss. * * @throws InvalidArgumentException * MUST be thrown if the $key string is not a legal value. */ public function get_data(string $key, $default = null) { $data = $this->cache->load(); if (!is_array($data)) { return $default; } // ignore data if internal cache expiration time is not set if (!array_key_exists('__cache_expiration_time', $data)) { return $default; } // ignore data if internal cache expiration time is expired if ($data['__cache_expiration_time'] < time()) { return $default; } // remove internal cache expiration time unset($data['__cache_expiration_time']); return $data; } /** * Persists data in the cache, uniquely referenced by a key with an optional expiration TTL time. * * Equivalent to \Psr\SimpleCache\CacheInterface::set() * <code> * public function set(string $key, mixed $value, null|int|\DateInterval $ttl = null): bool; * </code> * * @param string $key The key of the item to store. * @param array<mixed> $value The value of the item to store, must be serializable. * @param null|int $ttl Optional. The TTL value of this item. If no value is sent and * the driver supports TTL then the library may set a default value * for it or let the driver take care of that. * * @return bool True on success and false on failure. * * @throws InvalidArgumentException * MUST be thrown if the $key string is not a legal value. */ public function set_data(string $key, array $value, ?int $ttl = null): bool { if ($ttl === null) { $ttl = 3600; } // place internal cache expiration time $value['__cache_expiration_time'] = time() + $ttl; return $this->cache->save($value); } /** * Delete an item from the cache by its unique key. * * Equivalent to \Psr\SimpleCache\CacheInterface::delete() * <code> * public function delete(string $key): bool; * </code> * * @param string $key The unique cache key of the item to delete. * * @return bool True if the item was successfully removed. False if there was an error. * * @throws InvalidArgumentException * MUST be thrown if the $key string is not a legal value. */ public function delete_data(string $key): bool { return $this->cache->unlink(); } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Cache/Psr16.php�������������������������������������������������������������������������������������0000644�����������������00000006270�15250524467�0007212 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php // SPDX-FileCopyrightText: 2004-2023 Ryan Parman, Sam Sneddon, Ryan McCue // SPDX-License-Identifier: BSD-3-Clause declare(strict_types=1); namespace SimplePie\Cache; use Psr\SimpleCache\CacheInterface; use Psr\SimpleCache\InvalidArgumentException; use Throwable; /** * Caches data into a PSR-16 cache implementation * * @internal */ final class Psr16 implements DataCache { /** * PSR-16 cache implementation * * @var CacheInterface */ private $cache; /** * PSR-16 cache implementation * * @param CacheInterface $cache */ public function __construct(CacheInterface $cache) { $this->cache = $cache; } /** * Fetches a value from the cache. * * Equivalent to \Psr\SimpleCache\CacheInterface::get() * <code> * public function get(string $key, mixed $default = null): mixed; * </code> * * @param string $key The unique key of this item in the cache. * @param mixed $default Default value to return if the key does not exist. * * @return array|mixed The value of the item from the cache, or $default in case of cache miss. * * @throws InvalidArgumentException&Throwable * MUST be thrown if the $key string is not a legal value. */ public function get_data(string $key, $default = null) { $data = $this->cache->get($key, $default); if (!is_array($data) || $data === $default) { return $default; } return $data; } /** * Persists data in the cache, uniquely referenced by a key with an optional expiration TTL time. * * Equivalent to \Psr\SimpleCache\CacheInterface::set() * <code> * public function set(string $key, mixed $value, null|int|\DateInterval $ttl = null): bool; * </code> * * @param string $key The key of the item to store. * @param array<mixed> $value The value of the item to store, must be serializable. * @param null|int $ttl Optional. The TTL value of this item. If no value is sent and * the driver supports TTL then the library may set a default value * for it or let the driver take care of that. * * @return bool True on success and false on failure. * * @throws InvalidArgumentException&Throwable * MUST be thrown if the $key string is not a legal value. */ public function set_data(string $key, array $value, ?int $ttl = null): bool { return $this->cache->set($key, $value, $ttl); } /** * Delete an item from the cache by its unique key. * * Equivalent to \Psr\SimpleCache\CacheInterface::delete() * <code> * public function delete(string $key): bool; * </code> * * @param string $key The unique cache key of the item to delete. * * @return bool True if the item was successfully removed. False if there was an error. * * @throws InvalidArgumentException&Throwable * MUST be thrown if the $key string is not a legal value. */ public function delete_data(string $key): bool { return $this->cache->delete($key); } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Parser.php������������������������������������������������������������������������������������������0000644�����������������00000104064�15250524467�0006530 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php // SPDX-FileCopyrightText: 2004-2023 Ryan Parman, Sam Sneddon, Ryan McCue // SPDX-License-Identifier: BSD-3-Clause declare(strict_types=1); namespace SimplePie; use SimplePie\XML\Declaration\Parser as DeclarationParser; use XMLParser; /** * Parses XML into something sane * * * This class can be overloaded with {@see \SimplePie\SimplePie::set_parser_class()} */ class Parser implements RegistryAware { /** @var int */ public $error_code; /** @var string */ public $error_string; /** @var int */ public $current_line; /** @var int */ public $current_column; /** @var int */ public $current_byte; /** @var string */ public $separator = ' '; /** @var string[] */ public $namespace = ['']; /** @var string[] */ public $element = ['']; /** @var string[] */ public $xml_base = ['']; /** @var bool[] */ public $xml_base_explicit = [false]; /** @var string[] */ public $xml_lang = ['']; /** @var array<string, mixed> */ public $data = []; /** @var array<array<string, mixed>> */ public $datas = [[]]; /** @var int */ public $current_xhtml_construct = -1; /** @var string */ public $encoding; /** @var Registry */ protected $registry; /** * @return void */ public function set_registry(\SimplePie\Registry $registry) { $this->registry = $registry; } /** * @return bool */ public function parse(string &$data, string $encoding, string $url = '') { if (class_exists('DOMXpath') && function_exists('Mf2\parse')) { $doc = new \DOMDocument(); @$doc->loadHTML($data); $xpath = new \DOMXpath($doc); // Check for both h-feed and h-entry, as both a feed with no entries // and a list of entries without an h-feed wrapper are both valid. $query = '//*[contains(concat(" ", @class, " "), " h-feed ") or '. 'contains(concat(" ", @class, " "), " h-entry ")]'; /** @var \DOMNodeList<\DOMElement> $result */ $result = $xpath->query($query); if ($result->length !== 0) { return $this->parse_microformats($data, $url); } } // Use UTF-8 if we get passed US-ASCII, as every US-ASCII character is a UTF-8 character if (strtoupper($encoding) === 'US-ASCII') { $this->encoding = 'UTF-8'; } else { $this->encoding = $encoding; } // Strip BOM: // UTF-32 Big Endian BOM if (substr($data, 0, 4) === "\x00\x00\xFE\xFF") { $data = substr($data, 4); } // UTF-32 Little Endian BOM elseif (substr($data, 0, 4) === "\xFF\xFE\x00\x00") { $data = substr($data, 4); } // UTF-16 Big Endian BOM elseif (substr($data, 0, 2) === "\xFE\xFF") { $data = substr($data, 2); } // UTF-16 Little Endian BOM elseif (substr($data, 0, 2) === "\xFF\xFE") { $data = substr($data, 2); } // UTF-8 BOM elseif (substr($data, 0, 3) === "\xEF\xBB\xBF") { $data = substr($data, 3); } if (substr($data, 0, 5) === '<?xml' && strspn(substr($data, 5, 1), "\x09\x0A\x0D\x20") && ($pos = strpos($data, '?>')) !== false) { $declaration = $this->registry->create(DeclarationParser::class, [substr($data, 5, $pos - 5)]); if ($declaration->parse()) { $data = substr($data, $pos + 2); $data = '<?xml version="' . $declaration->version . '" encoding="' . $encoding . '" standalone="' . (($declaration->standalone) ? 'yes' : 'no') . '"?>' . "\n" . self::set_doctype($data); } else { $this->error_string = 'SimplePie bug! Please report this!'; return false; } } else { $data = self::set_doctype($data); } $return = true; static $xml_is_sane = null; if ($xml_is_sane === null) { $parser_check = xml_parser_create(); xml_parse_into_struct($parser_check, '<foo>&</foo>', $values); if (\PHP_VERSION_ID < 80000) { xml_parser_free($parser_check); } $xml_is_sane = isset($values[0]['value']); } // Create the parser if ($xml_is_sane) { $xml = xml_parser_create_ns($this->encoding, $this->separator); xml_parser_set_option($xml, XML_OPTION_SKIP_WHITE, 1); xml_parser_set_option($xml, XML_OPTION_CASE_FOLDING, 0); xml_set_character_data_handler($xml, [$this, 'cdata']); xml_set_element_handler($xml, [$this, 'tag_open'], [$this, 'tag_close']); // Parse! $wrapper = @is_writable(sys_get_temp_dir()) ? 'php://temp' : 'php://memory'; if (($stream = fopen($wrapper, 'r+')) && fwrite($stream, $data) && rewind($stream)) { //Parse by chunks not to use too much memory do { $stream_data = (string) fread($stream, 1048576); if (!xml_parse($xml, $stream_data, feof($stream))) { $this->error_code = xml_get_error_code($xml); $this->error_string = xml_error_string($this->error_code) ?: "Unknown"; $return = false; break; } } while (!feof($stream)); fclose($stream); } else { $return = false; } $this->current_line = xml_get_current_line_number($xml); $this->current_column = xml_get_current_column_number($xml); $this->current_byte = xml_get_current_byte_index($xml); if (\PHP_VERSION_ID < 80000) { xml_parser_free($xml); } return $return; } libxml_clear_errors(); $xml = new \XMLReader(); $xml->xml($data); while (@$xml->read()) { switch ($xml->nodeType) { case \XMLReader::END_ELEMENT: if ($xml->namespaceURI !== '') { $tagName = $xml->namespaceURI . $this->separator . $xml->localName; } else { $tagName = $xml->localName; } $this->tag_close(null, $tagName); break; case \XMLReader::ELEMENT: $empty = $xml->isEmptyElement; if ($xml->namespaceURI !== '') { $tagName = $xml->namespaceURI . $this->separator . $xml->localName; } else { $tagName = $xml->localName; } $attributes = []; while ($xml->moveToNextAttribute()) { if ($xml->namespaceURI !== '') { $attrName = $xml->namespaceURI . $this->separator . $xml->localName; } else { $attrName = $xml->localName; } $attributes[$attrName] = $xml->value; } $this->tag_open(null, $tagName, $attributes); if ($empty) { $this->tag_close(null, $tagName); } break; case \XMLReader::TEXT: case \XMLReader::CDATA: $this->cdata(null, $xml->value); break; } } if ($error = libxml_get_last_error()) { $this->error_code = $error->code; $this->error_string = $error->message; $this->current_line = $error->line; $this->current_column = $error->column; return false; } return true; } /** * @return int */ public function get_error_code() { return $this->error_code; } /** * @return string */ public function get_error_string() { return $this->error_string; } /** * @return int */ public function get_current_line() { return $this->current_line; } /** * @return int */ public function get_current_column() { return $this->current_column; } /** * @return int */ public function get_current_byte() { return $this->current_byte; } /** * @return array<string, mixed> */ public function get_data() { return $this->data; } /** * @param XMLParser|resource|null $parser * @param array<string, string> $attributes * @return void */ public function tag_open($parser, string $tag, array $attributes) { [$this->namespace[], $this->element[]] = $this->split_ns($tag); $attribs = []; foreach ($attributes as $name => $value) { [$attrib_namespace, $attribute] = $this->split_ns($name); $attribs[$attrib_namespace][$attribute] = $value; } if (isset($attribs[\SimplePie\SimplePie::NAMESPACE_XML]['base'])) { $base = $this->registry->call(Misc::class, 'absolutize_url', [$attribs[\SimplePie\SimplePie::NAMESPACE_XML]['base'], end($this->xml_base)]); if ($base !== false) { $this->xml_base[] = $base; $this->xml_base_explicit[] = true; } } else { $this->xml_base[] = end($this->xml_base) ?: ''; $this->xml_base_explicit[] = end($this->xml_base_explicit); } if (isset($attribs[\SimplePie\SimplePie::NAMESPACE_XML]['lang'])) { $this->xml_lang[] = $attribs[\SimplePie\SimplePie::NAMESPACE_XML]['lang']; } else { $this->xml_lang[] = end($this->xml_lang) ?: ''; } if ($this->current_xhtml_construct >= 0) { $this->current_xhtml_construct++; if (end($this->namespace) === \SimplePie\SimplePie::NAMESPACE_XHTML) { $this->data['data'] .= '<' . end($this->element); if (isset($attribs[''])) { foreach ($attribs[''] as $name => $value) { $this->data['data'] .= ' ' . $name . '="' . htmlspecialchars($value, ENT_COMPAT, $this->encoding) . '"'; } } $this->data['data'] .= '>'; } } else { $this->datas[] = &$this->data; $this->data = &$this->data['child'][end($this->namespace)][end($this->element)][]; $this->data = ['data' => '', 'attribs' => $attribs, 'xml_base' => end($this->xml_base), 'xml_base_explicit' => end($this->xml_base_explicit), 'xml_lang' => end($this->xml_lang)]; if ((end($this->namespace) === \SimplePie\SimplePie::NAMESPACE_ATOM_03 && in_array(end($this->element), ['title', 'tagline', 'copyright', 'info', 'summary', 'content']) && isset($attribs['']['mode']) && $attribs['']['mode'] === 'xml') || (end($this->namespace) === \SimplePie\SimplePie::NAMESPACE_ATOM_10 && in_array(end($this->element), ['rights', 'subtitle', 'summary', 'info', 'title', 'content']) && isset($attribs['']['type']) && $attribs['']['type'] === 'xhtml') || (end($this->namespace) === \SimplePie\SimplePie::NAMESPACE_RSS_20 && in_array(end($this->element), ['title'])) || (end($this->namespace) === \SimplePie\SimplePie::NAMESPACE_RSS_090 && in_array(end($this->element), ['title'])) || (end($this->namespace) === \SimplePie\SimplePie::NAMESPACE_RSS_10 && in_array(end($this->element), ['title']))) { $this->current_xhtml_construct = 0; } } } /** * @param XMLParser|resource|null $parser * @return void */ public function cdata($parser, string $cdata) { if ($this->current_xhtml_construct >= 0) { $this->data['data'] .= htmlspecialchars($cdata, ENT_QUOTES, $this->encoding); } else { $this->data['data'] .= $cdata; } } /** * @param XMLParser|resource|null $parser * @return void */ public function tag_close($parser, string $tag) { if ($this->current_xhtml_construct >= 0) { $this->current_xhtml_construct--; if (end($this->namespace) === \SimplePie\SimplePie::NAMESPACE_XHTML && !in_array(end($this->element), ['area', 'base', 'basefont', 'br', 'col', 'frame', 'hr', 'img', 'input', 'isindex', 'link', 'meta', 'param'])) { $this->data['data'] .= '</' . end($this->element) . '>'; } } if ($this->current_xhtml_construct === -1) { $this->data = &$this->datas[count($this->datas) - 1]; array_pop($this->datas); } array_pop($this->element); array_pop($this->namespace); array_pop($this->xml_base); array_pop($this->xml_base_explicit); array_pop($this->xml_lang); } /** * @return array{string, string} */ public function split_ns(string $string) { static $cache = []; if (!isset($cache[$string])) { if ($pos = strpos($string, $this->separator)) { static $separator_length; if (!$separator_length) { $separator_length = strlen($this->separator); } $namespace = substr($string, 0, $pos); $local_name = substr($string, $pos + $separator_length); if (strtolower($namespace) === \SimplePie\SimplePie::NAMESPACE_ITUNES) { $namespace = \SimplePie\SimplePie::NAMESPACE_ITUNES; } // Normalize the Media RSS namespaces if ($namespace === \SimplePie\SimplePie::NAMESPACE_MEDIARSS_WRONG || $namespace === \SimplePie\SimplePie::NAMESPACE_MEDIARSS_WRONG2 || $namespace === \SimplePie\SimplePie::NAMESPACE_MEDIARSS_WRONG3 || $namespace === \SimplePie\SimplePie::NAMESPACE_MEDIARSS_WRONG4 || $namespace === \SimplePie\SimplePie::NAMESPACE_MEDIARSS_WRONG5) { $namespace = \SimplePie\SimplePie::NAMESPACE_MEDIARSS; } $cache[$string] = [$namespace, $local_name]; } else { $cache[$string] = ['', $string]; } } return $cache[$string]; } /** * @param array<string, mixed> $data */ private function parse_hcard(array $data, bool $category = false): string { $name = ''; $link = ''; // Check if h-card is set and pass that information on in the link. if (isset($data['type']) && in_array('h-card', $data['type'])) { if (isset($data['properties']['name'][0])) { $name = $data['properties']['name'][0]; } if (isset($data['properties']['url'][0])) { $link = $data['properties']['url'][0]; if ($name === '') { $name = $link; } else { // can't have commas in categories. $name = str_replace(',', '', $name); } $person_tag = $category ? '<span class="person-tag"></span>' : ''; return '<a class="h-card" href="'.$link.'">'.$person_tag.$name.'</a>'; } } return $data['value'] ?? ''; } /** * @return true */ private function parse_microformats(string &$data, string $url): bool { // For PHPStan, we already check that in call site. \assert(function_exists('Mf2\parse')); \assert(function_exists('Mf2\fetch')); $feed_title = ''; $feed_author = null; $author_cache = []; $items = []; $entries = []; $mf = \Mf2\parse($data, $url); // First look for an h-feed. $h_feed = []; foreach ($mf['items'] as $mf_item) { if (in_array('h-feed', $mf_item['type'])) { $h_feed = $mf_item; break; } // Also look for h-feed or h-entry in the children of each top level item. if (!isset($mf_item['children'][0]['type'])) { continue; } if (in_array('h-feed', $mf_item['children'][0]['type'])) { $h_feed = $mf_item['children'][0]; // In this case the parent of the h-feed may be an h-card, so use it as // the feed_author. if (in_array('h-card', $mf_item['type'])) { $feed_author = $mf_item; } break; } elseif (in_array('h-entry', $mf_item['children'][0]['type'])) { $entries = $mf_item['children']; // In this case the parent of the h-entry list may be an h-card, so use // it as the feed_author. if (in_array('h-card', $mf_item['type'])) { $feed_author = $mf_item; } break; } } if (isset($h_feed['children'])) { $entries = $h_feed['children']; // Also set the feed title and store author from the h-feed if available. if (isset($mf['items'][0]['properties']['name'][0])) { $feed_title = $mf['items'][0]['properties']['name'][0]; } if (isset($mf['items'][0]['properties']['author'][0])) { $feed_author = $mf['items'][0]['properties']['author'][0]; } } elseif (count($entries) === 0) { $entries = $mf['items']; } for ($i = 0; $i < count($entries); $i++) { $entry = $entries[$i]; if (in_array('h-entry', $entry['type'])) { $item = []; $title = ''; $description = ''; if (isset($entry['properties']['url'][0])) { $link = $entry['properties']['url'][0]; if (isset($link['value'])) { $link = $link['value']; } $item['link'] = [['data' => $link]]; } if (isset($entry['properties']['uid'][0])) { $guid = $entry['properties']['uid'][0]; if (isset($guid['value'])) { $guid = $guid['value']; } $item['guid'] = [['data' => $guid]]; } if (isset($entry['properties']['name'][0])) { $title = $entry['properties']['name'][0]; if (isset($title['value'])) { $title = $title['value']; } $item['title'] = [['data' => $title]]; } if (isset($entry['properties']['author'][0]) || isset($feed_author)) { // author is a special case, it can be plain text or an h-card array. // If it's plain text it can also be a url that should be followed to // get the actual h-card. $author = $entry['properties']['author'][0] ?? $feed_author; if (!is_string($author)) { $author = $this->parse_hcard($author); } elseif (strpos($author, 'http') === 0) { if (isset($author_cache[$author])) { $author = $author_cache[$author]; } else { if ($mf = \Mf2\fetch($author)) { foreach ($mf['items'] as $hcard) { // Only interested in an h-card by itself in this case. if (!in_array('h-card', $hcard['type'])) { continue; } // It must have a url property matching what we fetched. if (!isset($hcard['properties']['url']) || !(in_array($author, $hcard['properties']['url']))) { continue; } // Save parse_hcard the trouble of finding the correct url. $hcard['properties']['url'][0] = $author; // Cache this h-card for the next h-entry to check. $author_cache[$author] = $this->parse_hcard($hcard); $author = $author_cache[$author]; break; } } } } $item['author'] = [['data' => $author]]; } if (isset($entry['properties']['photo'][0])) { // If a photo is also in content, don't need to add it again here. $content = ''; if (isset($entry['properties']['content'][0]['html'])) { $content = $entry['properties']['content'][0]['html']; } $photo_list = []; for ($j = 0; $j < count($entry['properties']['photo']); $j++) { $photo = $entry['properties']['photo'][$j]; if (!empty($photo) && strpos($content, $photo) === false) { $photo_list[] = $photo; } } // When there's more than one photo show the first and use a lightbox. // Need a permanent, unique name for the image set, but don't have // anything unique except for the content itself, so use that. $count = count($photo_list); if ($count > 1) { $image_set_id = preg_replace('/[[:^alnum:]]/', '', $photo_list[0]); $description = '<p>'; for ($j = 0; $j < $count; $j++) { $hidden = $j === 0 ? '' : 'class="hidden" '; $description .= '<a href="'.$photo_list[$j].'" '.$hidden. 'data-lightbox="image-set-'.$image_set_id.'">'. '<img src="'.$photo_list[$j].'"></a>'; } $description .= '<br><b>'.$count.' photos</b></p>'; } elseif ($count == 1) { $description = '<p><img src="'.$photo_list[0].'"></p>'; } } if (isset($entry['properties']['content'][0]['html'])) { // e-content['value'] is the same as p-name when they are on the same // element. Use this to replace title with a strip_tags version so // that alt text from images is not included in the title. if ($entry['properties']['content'][0]['value'] === $title) { $title = strip_tags($entry['properties']['content'][0]['html']); $item['title'] = [['data' => $title]]; } $description .= $entry['properties']['content'][0]['html']; if (isset($entry['properties']['in-reply-to'][0])) { $in_reply_to = ''; if (is_string($entry['properties']['in-reply-to'][0])) { $in_reply_to = $entry['properties']['in-reply-to'][0]; } elseif (isset($entry['properties']['in-reply-to'][0]['value'])) { $in_reply_to = $entry['properties']['in-reply-to'][0]['value']; } if ($in_reply_to !== '') { $description .= '<p><span class="in-reply-to"></span> '. '<a href="'.$in_reply_to.'">'.$in_reply_to.'</a><p>'; } } $item['description'] = [['data' => $description]]; } if (isset($entry['properties']['category'])) { $category_csv = ''; // Categories can also contain h-cards. foreach ($entry['properties']['category'] as $category) { if ($category_csv !== '') { $category_csv .= ', '; } if (is_string($category)) { // Can't have commas in categories. $category_csv .= str_replace(',', '', $category); } else { $category_csv .= $this->parse_hcard($category, true); } } $item['category'] = [['data' => $category_csv]]; } if (isset($entry['properties']['published'][0])) { $timestamp = strtotime($entry['properties']['published'][0]); $pub_date = date('F j Y g:ia', $timestamp).' GMT'; $item['pubDate'] = [['data' => $pub_date]]; } // The title and description are set to the empty string to represent // a deleted item (which also makes it an invalid rss item). if (isset($entry['properties']['deleted'][0])) { $item['title'] = [['data' => '']]; $item['description'] = [['data' => '']]; } $items[] = ['child' => ['' => $item]]; } } // Mimic RSS data format when storing microformats. $link = [['data' => $url]]; $image = ''; if (!is_string($feed_author) && isset($feed_author['properties']['photo'][0])) { $image = [['child' => ['' => ['url' => [['data' => $feed_author['properties']['photo'][0]]]]]]]; } // Use the name given for the h-feed, or get the title from the html. if ($feed_title !== '') { $feed_title = [['data' => htmlspecialchars($feed_title)]]; } elseif ($position = strpos($data, '<title>')) { $start = $position < 200 ? 0 : $position - 200; $check = substr($data, $start, 400); $matches = []; if (preg_match('/<title>(.+)<\/title>/', $check, $matches)) { $feed_title = [['data' => htmlspecialchars($matches[1])]]; } } $channel = ['channel' => [['child' => ['' => ['link' => $link, 'image' => $image, 'title' => $feed_title, 'item' => $items]]]]]; $rss = [['attribs' => ['' => ['version' => '2.0']], 'child' => ['' => $channel]]]; $this->data = ['child' => ['' => ['rss' => $rss]]]; return true; } private static function set_doctype(string $data): string { // Strip DOCTYPE except if containing an [internal subset] $data = preg_replace('/^\\s*<!DOCTYPE\\s[^>\\[\\]]*>\s*/', '', $data) ?? $data; // Declare HTML entities only if no remaining DOCTYPE $doctype = preg_match('/^\\s*<!DOCTYPE\\s/', $data) ? '' : self::declare_html_entities(); return $doctype . $data; } private static function declare_html_entities(): string { // This is required because the RSS specification says that entity-encoded // html is allowed, but the xml specification says they must be declared. return '<!DOCTYPE rss [ <!ENTITY nbsp " "> <!ENTITY iexcl "¡"> <!ENTITY cent "¢"> <!ENTITY pound "£"> <!ENTITY curren "¤"> <!ENTITY yen "¥"> <!ENTITY brvbar "¦"> <!ENTITY sect "§"> <!ENTITY uml "¨"> <!ENTITY copy "©"> <!ENTITY ordf "ª"> <!ENTITY laquo "«"> <!ENTITY not "¬"> <!ENTITY shy "­"> <!ENTITY reg "®"> <!ENTITY macr "¯"> <!ENTITY deg "°"> <!ENTITY plusmn "±"> <!ENTITY sup2 "²"> <!ENTITY sup3 "³"> <!ENTITY acute "´"> <!ENTITY micro "µ"> <!ENTITY para "¶"> <!ENTITY middot "·"> <!ENTITY cedil "¸"> <!ENTITY sup1 "¹"> <!ENTITY ordm "º"> <!ENTITY raquo "»"> <!ENTITY frac14 "¼"> <!ENTITY frac12 "½"> <!ENTITY frac34 "¾"> <!ENTITY iquest "¿"> <!ENTITY Agrave "À"> <!ENTITY Aacute "Á"> <!ENTITY Acirc "Â"> <!ENTITY Atilde "Ã"> <!ENTITY Auml "Ä"> <!ENTITY Aring "Å"> <!ENTITY AElig "Æ"> <!ENTITY Ccedil "Ç"> <!ENTITY Egrave "È"> <!ENTITY Eacute "É"> <!ENTITY Ecirc "Ê"> <!ENTITY Euml "Ë"> <!ENTITY Igrave "Ì"> <!ENTITY Iacute "Í"> <!ENTITY Icirc "Î"> <!ENTITY Iuml "Ï"> <!ENTITY ETH "Ð"> <!ENTITY Ntilde "Ñ"> <!ENTITY Ograve "Ò"> <!ENTITY Oacute "Ó"> <!ENTITY Ocirc "Ô"> <!ENTITY Otilde "Õ"> <!ENTITY Ouml "Ö"> <!ENTITY times "×"> <!ENTITY Oslash "Ø"> <!ENTITY Ugrave "Ù"> <!ENTITY Uacute "Ú"> <!ENTITY Ucirc "Û"> <!ENTITY Uuml "Ü"> <!ENTITY Yacute "Ý"> <!ENTITY THORN "Þ"> <!ENTITY szlig "ß"> <!ENTITY agrave "à"> <!ENTITY aacute "á"> <!ENTITY acirc "â"> <!ENTITY atilde "ã"> <!ENTITY auml "ä"> <!ENTITY aring "å"> <!ENTITY aelig "æ"> <!ENTITY ccedil "ç"> <!ENTITY egrave "è"> <!ENTITY eacute "é"> <!ENTITY ecirc "ê"> <!ENTITY euml "ë"> <!ENTITY igrave "ì"> <!ENTITY iacute "í"> <!ENTITY icirc "î"> <!ENTITY iuml "ï"> <!ENTITY eth "ð"> <!ENTITY ntilde "ñ"> <!ENTITY ograve "ò"> <!ENTITY oacute "ó"> <!ENTITY ocirc "ô"> <!ENTITY otilde "õ"> <!ENTITY ouml "ö"> <!ENTITY divide "÷"> <!ENTITY oslash "ø"> <!ENTITY ugrave "ù"> <!ENTITY uacute "ú"> <!ENTITY ucirc "û"> <!ENTITY uuml "ü"> <!ENTITY yacute "ý"> <!ENTITY thorn "þ"> <!ENTITY yuml "ÿ"> <!ENTITY OElig "Œ"> <!ENTITY oelig "œ"> <!ENTITY Scaron "Š"> <!ENTITY scaron "š"> <!ENTITY Yuml "Ÿ"> <!ENTITY fnof "ƒ"> <!ENTITY circ "ˆ"> <!ENTITY tilde "˜"> <!ENTITY Alpha "Α"> <!ENTITY Beta "Β"> <!ENTITY Gamma "Γ"> <!ENTITY Epsilon "Ε"> <!ENTITY Zeta "Ζ"> <!ENTITY Eta "Η"> <!ENTITY Theta "Θ"> <!ENTITY Iota "Ι"> <!ENTITY Kappa "Κ"> <!ENTITY Lambda "Λ"> <!ENTITY Mu "Μ"> <!ENTITY Nu "Ν"> <!ENTITY Xi "Ξ"> <!ENTITY Omicron "Ο"> <!ENTITY Pi "Π"> <!ENTITY Rho "Ρ"> <!ENTITY Sigma "Σ"> <!ENTITY Tau "Τ"> <!ENTITY Upsilon "Υ"> <!ENTITY Phi "Φ"> <!ENTITY Chi "Χ"> <!ENTITY Psi "Ψ"> <!ENTITY Omega "Ω"> <!ENTITY alpha "α"> <!ENTITY beta "β"> <!ENTITY gamma "γ"> <!ENTITY delta "δ"> <!ENTITY epsilon "ε"> <!ENTITY zeta "ζ"> <!ENTITY eta "η"> <!ENTITY theta "θ"> <!ENTITY iota "ι"> <!ENTITY kappa "κ"> <!ENTITY lambda "λ"> <!ENTITY mu "μ"> <!ENTITY nu "ν"> <!ENTITY xi "ξ"> <!ENTITY omicron "ο"> <!ENTITY pi "π"> <!ENTITY rho "ρ"> <!ENTITY sigmaf "ς"> <!ENTITY sigma "σ"> <!ENTITY tau "τ"> <!ENTITY upsilon "υ"> <!ENTITY phi "φ"> <!ENTITY chi "χ"> <!ENTITY psi "ψ"> <!ENTITY omega "ω"> <!ENTITY thetasym "ϑ"> <!ENTITY upsih "ϒ"> <!ENTITY piv "ϖ"> <!ENTITY ensp " "> <!ENTITY emsp " "> <!ENTITY thinsp " "> <!ENTITY zwnj "‌"> <!ENTITY zwj "‍"> <!ENTITY lrm "‎"> <!ENTITY rlm "‏"> <!ENTITY ndash "–"> <!ENTITY mdash "—"> <!ENTITY lsquo "‘"> <!ENTITY rsquo "’"> <!ENTITY sbquo "‚"> <!ENTITY ldquo "“"> <!ENTITY rdquo "”"> <!ENTITY bdquo "„"> <!ENTITY dagger "†"> <!ENTITY Dagger "‡"> <!ENTITY bull "•"> <!ENTITY hellip "…"> <!ENTITY permil "‰"> <!ENTITY prime "′"> <!ENTITY Prime "″"> <!ENTITY lsaquo "‹"> <!ENTITY rsaquo "›"> <!ENTITY oline "‾"> <!ENTITY frasl "⁄"> <!ENTITY euro "€"> <!ENTITY image "ℑ"> <!ENTITY weierp "℘"> <!ENTITY real "ℜ"> <!ENTITY trade "™"> <!ENTITY alefsym "ℵ"> <!ENTITY larr "←"> <!ENTITY uarr "↑"> <!ENTITY rarr "→"> <!ENTITY darr "↓"> <!ENTITY harr "↔"> <!ENTITY crarr "↵"> <!ENTITY lArr "⇐"> <!ENTITY uArr "⇑"> <!ENTITY rArr "⇒"> <!ENTITY dArr "⇓"> <!ENTITY hArr "⇔"> <!ENTITY forall "∀"> <!ENTITY part "∂"> <!ENTITY exist "∃"> <!ENTITY empty "∅"> <!ENTITY nabla "∇"> <!ENTITY isin "∈"> <!ENTITY notin "∉"> <!ENTITY ni "∋"> <!ENTITY prod "∏"> <!ENTITY sum "∑"> <!ENTITY minus "−"> <!ENTITY lowast "∗"> <!ENTITY radic "√"> <!ENTITY prop "∝"> <!ENTITY infin "∞"> <!ENTITY ang "∠"> <!ENTITY and "∧"> <!ENTITY or "∨"> <!ENTITY cap "∩"> <!ENTITY cup "∪"> <!ENTITY int "∫"> <!ENTITY there4 "∴"> <!ENTITY sim "∼"> <!ENTITY cong "≅"> <!ENTITY asymp "≈"> <!ENTITY ne "≠"> <!ENTITY equiv "≡"> <!ENTITY le "≤"> <!ENTITY ge "≥"> <!ENTITY sub "⊂"> <!ENTITY sup "⊃"> <!ENTITY nsub "⊄"> <!ENTITY sube "⊆"> <!ENTITY supe "⊇"> <!ENTITY oplus "⊕"> <!ENTITY otimes "⊗"> <!ENTITY perp "⊥"> <!ENTITY sdot "⋅"> <!ENTITY lceil "⌈"> <!ENTITY rceil "⌉"> <!ENTITY lfloor "⌊"> <!ENTITY rfloor "⌋"> <!ENTITY lang "〈"> <!ENTITY rang "〉"> <!ENTITY loz "◊"> <!ENTITY spades "♠"> <!ENTITY clubs "♣"> <!ENTITY hearts "♥"> <!ENTITY diams "♦"> ]>'; } } class_alias('SimplePie\Parser', 'SimplePie_Parser'); ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Copyright.php���������������������������������������������������������������������������������������0000644�����������������00000003262�15250524467�0007242 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php // SPDX-FileCopyrightText: 2004-2023 Ryan Parman, Sam Sneddon, Ryan McCue // SPDX-License-Identifier: BSD-3-Clause declare(strict_types=1); namespace SimplePie; /** * Manages `<media:copyright>` copyright tags as defined in Media RSS * * Used by {@see \SimplePie\Enclosure::get_copyright()} * * This class can be overloaded with {@see \SimplePie\SimplePie::set_copyright_class()} */ class Copyright { /** * Copyright URL * * @var ?string * @see get_url() */ public $url; /** * Attribution * * @var ?string * @see get_attribution() */ public $label; /** * Constructor, used to input the data * * For documentation on all the parameters, see the corresponding * properties and their accessors */ public function __construct( ?string $url = null, ?string $label = null ) { $this->url = $url; $this->label = $label; } /** * String-ified version * * @return string */ public function __toString() { // There is no $this->data here return md5(serialize($this)); } /** * Get the copyright URL * * @return string|null URL to copyright information */ public function get_url() { if ($this->url !== null) { return $this->url; } return null; } /** * Get the attribution text * * @return string|null */ public function get_attribution() { if ($this->label !== null) { return $this->label; } return null; } } class_alias('SimplePie\Copyright', 'SimplePie_Copyright'); ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������XML/Declaration/Parser.php��������������������������������������������������������������������������0000644�����������������00000017100�15250524467�0011407 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php // SPDX-FileCopyrightText: 2004-2023 Ryan Parman, Sam Sneddon, Ryan McCue // SPDX-License-Identifier: BSD-3-Clause declare(strict_types=1); namespace SimplePie\XML\Declaration; /** * Parses the XML Declaration */ class Parser { /** * XML Version * * @access public * @var string */ public $version = '1.0'; /** * Encoding * * @access public * @var string */ public $encoding = 'UTF-8'; /** * Standalone * * @access public * @var bool */ public $standalone = false; private const STATE_BEFORE_VERSION_NAME = 'before_version_name'; private const STATE_VERSION_NAME = 'version_name'; private const STATE_VERSION_EQUALS = 'version_equals'; private const STATE_VERSION_VALUE = 'version_value'; private const STATE_ENCODING_NAME = 'encoding_name'; private const STATE_EMIT = 'emit'; private const STATE_ENCODING_EQUALS = 'encoding_equals'; private const STATE_STANDALONE_NAME = 'standalone_name'; private const STATE_ENCODING_VALUE = 'encoding_value'; private const STATE_STANDALONE_EQUALS = 'standalone_equals'; private const STATE_STANDALONE_VALUE = 'standalone_value'; private const STATE_ERROR = false; /** * Current state of the state machine * * @access private * @var self::STATE_* */ public $state = self::STATE_BEFORE_VERSION_NAME; /** * Input data * * @access private * @var string */ public $data = ''; /** * Input data length (to avoid calling strlen() everytime this is needed) * * @access private * @var int */ public $data_length = 0; /** * Current position of the pointer * * @var int * @access private */ public $position = 0; /** * Create an instance of the class with the input data * * @access public * @param string $data Input data */ public function __construct(string $data) { $this->data = $data; $this->data_length = strlen($this->data); } /** * Parse the input data * * @access public * @return bool true on success, false on failure */ public function parse(): bool { while ($this->state && $this->state !== self::STATE_EMIT && $this->has_data()) { $state = $this->state; $this->$state(); } $this->data = ''; if ($this->state === self::STATE_EMIT) { return true; } // Reset the parser state. $this->version = '1.0'; $this->encoding = 'UTF-8'; $this->standalone = false; return false; } /** * Check whether there is data beyond the pointer * * @access private * @return bool true if there is further data, false if not */ public function has_data(): bool { return (bool) ($this->position < $this->data_length); } /** * Advance past any whitespace * * @return int Number of whitespace characters passed */ public function skip_whitespace() { $whitespace = strspn($this->data, "\x09\x0A\x0D\x20", $this->position); $this->position += $whitespace; return $whitespace; } /** * Read value * * @return string|false */ public function get_value() { $quote = substr($this->data, $this->position, 1); if ($quote === '"' || $quote === "'") { $this->position++; $len = strcspn($this->data, $quote, $this->position); if ($this->has_data()) { $value = substr($this->data, $this->position, $len); $this->position += $len + 1; return $value; } } return false; } public function before_version_name(): void { if ($this->skip_whitespace()) { $this->state = self::STATE_VERSION_NAME; } else { $this->state = self::STATE_ERROR; } } public function version_name(): void { if (substr($this->data, $this->position, 7) === 'version') { $this->position += 7; $this->skip_whitespace(); $this->state = self::STATE_VERSION_EQUALS; } else { $this->state = self::STATE_ERROR; } } public function version_equals(): void { if (substr($this->data, $this->position, 1) === '=') { $this->position++; $this->skip_whitespace(); $this->state = self::STATE_VERSION_VALUE; } else { $this->state = self::STATE_ERROR; } } public function version_value(): void { if ($version = $this->get_value()) { $this->version = $version; $this->skip_whitespace(); if ($this->has_data()) { $this->state = self::STATE_ENCODING_NAME; } else { $this->state = self::STATE_EMIT; } } else { $this->state = self::STATE_ERROR; } } public function encoding_name(): void { if (substr($this->data, $this->position, 8) === 'encoding') { $this->position += 8; $this->skip_whitespace(); $this->state = self::STATE_ENCODING_EQUALS; } else { $this->state = self::STATE_STANDALONE_NAME; } } public function encoding_equals(): void { if (substr($this->data, $this->position, 1) === '=') { $this->position++; $this->skip_whitespace(); $this->state = self::STATE_ENCODING_VALUE; } else { $this->state = self::STATE_ERROR; } } public function encoding_value(): void { if ($encoding = $this->get_value()) { $this->encoding = $encoding; $this->skip_whitespace(); if ($this->has_data()) { $this->state = self::STATE_STANDALONE_NAME; } else { $this->state = self::STATE_EMIT; } } else { $this->state = self::STATE_ERROR; } } public function standalone_name(): void { if (substr($this->data, $this->position, 10) === 'standalone') { $this->position += 10; $this->skip_whitespace(); $this->state = self::STATE_STANDALONE_EQUALS; } else { $this->state = self::STATE_ERROR; } } public function standalone_equals(): void { if (substr($this->data, $this->position, 1) === '=') { $this->position++; $this->skip_whitespace(); $this->state = self::STATE_STANDALONE_VALUE; } else { $this->state = self::STATE_ERROR; } } public function standalone_value(): void { if ($standalone = $this->get_value()) { switch ($standalone) { case 'yes': $this->standalone = true; break; case 'no': $this->standalone = false; break; default: $this->state = self::STATE_ERROR; return; } $this->skip_whitespace(); if ($this->has_data()) { $this->state = self::STATE_ERROR; } else { $this->state = self::STATE_EMIT; } } else { $this->state = self::STATE_ERROR; } } } class_alias('SimplePie\XML\Declaration\Parser', 'SimplePie_XML_Declaration_Parser'); ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Gzdecode.php����������������������������������������������������������������������������������������0000644�����������������00000020500�15250524467�0007010 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php // SPDX-FileCopyrightText: 2004-2023 Ryan Parman, Sam Sneddon, Ryan McCue // SPDX-License-Identifier: BSD-3-Clause declare(strict_types=1); namespace SimplePie; /** * Decode 'gzip' encoded HTTP data * * @link http://www.gzip.org/format.txt * @link https://www.php.net/manual/en/function.gzdecode.php * @deprecated since SimplePie 1.9.0, use `gzdecode` function instead. */ class Gzdecode { /** * Compressed data * * @access private * @var string * @see gzdecode::$data */ public $compressed_data; /** * Size of compressed data * * @access private * @var int */ public $compressed_size; /** * Minimum size of a valid gzip string * * @access private * @var int */ public $min_compressed_size = 18; /** * Current position of pointer * * @access private * @var int */ public $position = 0; /** * Flags (FLG) * * @access private * @var int */ public $flags; /** * Uncompressed data * * @access public * @see gzdecode::$compressed_data * @var string */ public $data; /** * Modified time * * @access public * @var int */ public $MTIME; /** * Extra Flags * * @access public * @var int */ public $XFL; /** * Operating System * * @access public * @var int */ public $OS; /** * Subfield ID 1 * * @access public * @see gzdecode::$extra_field * @see gzdecode::$SI2 * @var string */ public $SI1; /** * Subfield ID 2 * * @access public * @see gzdecode::$extra_field * @see gzdecode::$SI1 * @var string */ public $SI2; /** * Extra field content * * @access public * @see gzdecode::$SI1 * @see gzdecode::$SI2 * @var string */ public $extra_field; /** * Original filename * * @access public * @var string */ public $filename; /** * Human readable comment * * @access public * @var string */ public $comment; /** * Don't allow anything to be set * * @param string $name * @param mixed $value */ public function __set(string $name, $value) { throw new Exception("Cannot write property $name"); } /** * Set the compressed string and related properties * * @param string $data */ public function __construct(string $data) { $this->compressed_data = $data; $this->compressed_size = strlen($data); } /** * Decode the GZIP stream * * @return bool Successfulness */ public function parse() { if ($this->compressed_size >= $this->min_compressed_size) { $len = 0; // Check ID1, ID2, and CM if (substr($this->compressed_data, 0, 3) !== "\x1F\x8B\x08") { return false; } // Get the FLG (FLaGs) $this->flags = ord($this->compressed_data[3]); // FLG bits above (1 << 4) are reserved if ($this->flags > 0x1F) { return false; } // Advance the pointer after the above $this->position += 4; // MTIME $mtime = substr($this->compressed_data, $this->position, 4); // Reverse the string if we're on a big-endian arch because l is the only signed long and is machine endianness if (current((array) unpack('S', "\x00\x01")) === 1) { $mtime = strrev($mtime); } $this->MTIME = current((array) unpack('l', $mtime)); $this->position += 4; // Get the XFL (eXtra FLags) $this->XFL = ord($this->compressed_data[$this->position++]); // Get the OS (Operating System) $this->OS = ord($this->compressed_data[$this->position++]); // Parse the FEXTRA if ($this->flags & 4) { // Read subfield IDs $this->SI1 = $this->compressed_data[$this->position++]; $this->SI2 = $this->compressed_data[$this->position++]; // SI2 set to zero is reserved for future use if ($this->SI2 === "\x00") { return false; } // Get the length of the extra field $len = current((array) unpack('v', substr($this->compressed_data, $this->position, 2))); $this->position += 2; // Check the length of the string is still valid $this->min_compressed_size += $len + 4; if ($this->compressed_size >= $this->min_compressed_size) { // Set the extra field to the given data $this->extra_field = substr($this->compressed_data, $this->position, $len); $this->position += $len; } else { return false; } } // Parse the FNAME if ($this->flags & 8) { // Get the length of the filename $len = strcspn($this->compressed_data, "\x00", $this->position); // Check the length of the string is still valid $this->min_compressed_size += $len + 1; if ($this->compressed_size >= $this->min_compressed_size) { // Set the original filename to the given string $this->filename = substr($this->compressed_data, $this->position, $len); $this->position += $len + 1; } else { return false; } } // Parse the FCOMMENT if ($this->flags & 16) { // Get the length of the comment $len = strcspn($this->compressed_data, "\x00", $this->position); // Check the length of the string is still valid $this->min_compressed_size += $len + 1; if ($this->compressed_size >= $this->min_compressed_size) { // Set the original comment to the given string $this->comment = substr($this->compressed_data, $this->position, $len); $this->position += $len + 1; } else { return false; } } // Parse the FHCRC if ($this->flags & 2) { // Check the length of the string is still valid $this->min_compressed_size += $len + 2; if ($this->compressed_size >= $this->min_compressed_size) { // Read the CRC $crc = current((array) unpack('v', substr($this->compressed_data, $this->position, 2))); // Check the CRC matches if ((crc32(substr($this->compressed_data, 0, $this->position)) & 0xFFFF) === $crc) { $this->position += 2; } else { return false; } } else { return false; } } // Decompress the actual data if (($data = gzinflate(substr($this->compressed_data, $this->position, -8))) === false) { return false; } $this->data = $data; $this->position = $this->compressed_size - 8; // Check CRC of data $crc = current((array) unpack('V', substr($this->compressed_data, $this->position, 4))); $this->position += 4; /*if (extension_loaded('hash') && sprintf('%u', current(unpack('V', hash('crc32b', $this->data)))) !== sprintf('%u', $crc)) { return false; }*/ // Check ISIZE of data $isize = current((array) unpack('V', substr($this->compressed_data, $this->position, 4))); $this->position += 4; if (sprintf('%u', strlen($this->data) & 0xFFFFFFFF) !== sprintf('%u', $isize)) { return false; } // Wow, against all odds, we've actually got a valid gzip string return true; } return false; } } class_alias('SimplePie\Gzdecode', 'SimplePie_gzdecode'); ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Restriction.php�������������������������������������������������������������������������������������0000644�����������������00000004501�15250524467�0007574 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php // SPDX-FileCopyrightText: 2004-2023 Ryan Parman, Sam Sneddon, Ryan McCue // SPDX-License-Identifier: BSD-3-Clause declare(strict_types=1); namespace SimplePie; /** * Handles `<media:restriction>` as defined in Media RSS * * Used by {@see \SimplePie\Enclosure::get_restriction()} and {@see \SimplePie\Enclosure::get_restrictions()} * * This class can be overloaded with {@see \SimplePie\SimplePie::set_restriction_class()} */ class Restriction { public const RELATIONSHIP_ALLOW = 'allow'; public const RELATIONSHIP_DENY = 'deny'; /** * Relationship ('allow'/'deny') * * @var self::RELATIONSHIP_*|null * @see get_relationship() */ public $relationship; /** * Type of restriction * * @var string|null * @see get_type() */ public $type; /** * Restricted values * * @var string|null * @see get_value() */ public $value; /** * Constructor, used to input the data * * For documentation on all the parameters, see the corresponding * properties and their accessors * * @param ?self::RELATIONSHIP_* $relationship */ public function __construct(?string $relationship = null, ?string $type = null, ?string $value = null) { $this->relationship = $relationship; $this->type = $type; $this->value = $value; } /** * String-ified version * * @return string */ public function __toString() { // There is no $this->data here return md5(serialize($this)); } /** * Get the relationship * * @return ?self::RELATIONSHIP_* */ public function get_relationship() { if ($this->relationship !== null) { return $this->relationship; } return null; } /** * Get the type * * @return string|null */ public function get_type() { if ($this->type !== null) { return $this->type; } return null; } /** * Get the list of restricted things * * @return string|null */ public function get_value() { if ($this->value !== null) { return $this->value; } return null; } } class_alias('SimplePie\Restriction', 'SimplePie_Restriction'); �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Common/Contracts/AiClientExceptionInterface.php�����������������������������������������������������0000644�����������������00000000527�15250554011�0015637 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Common\Contracts; use Throwable; /** * Base interface for all AI Client exceptions. * * This interface allows callers to catch all AI Client specific exceptions * with a single catch statement. * * @since 0.2.0 */ interface AiClientExceptionInterface extends Throwable { } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������Common/Contracts/WithJsonSchemaInterface.php��������������������������������������������������������0000644�����������������00000001136�15250554011�0015153 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Common\Contracts; /** * Interface for objects that can provide their JSON schema representation. * * This interface is implemented by DTOs to provide a consistent way to retrieve * their JSON schema for validation and serialization purposes. * * @since 0.1.0 */ interface WithJsonSchemaInterface { /** * Gets the JSON schema representation of the object. * * @since 0.1.0 * * @return array<string, mixed> The JSON schema as an associative array. */ public static function getJsonSchema(): array; } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Common/Contracts/WithArrayTransformationInterface.php�����������������������������������������������0000644�����������������00000002033�15250554011�0017123 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Common\Contracts; /** * Interface for objects that support array transformation. * * @since 0.1.0 * * @template TArrayShape of array<string, mixed> */ interface WithArrayTransformationInterface { /** * Converts the object to an array representation. * * @since 0.1.0 * * @return TArrayShape The array representation. */ public function toArray(): array; /** * Creates an instance from array data. * * @since 0.1.0 * * @param TArrayShape $array The array data. * @return self<TArrayShape> The created instance. */ public static function fromArray(array $array): self; /** * Checks if the array is a valid shape for this object. * * @since 0.1.0 * * @param array<mixed> $array The array to check. * @return bool True if the array is a valid shape. * @phpstan-assert-if-true TArrayShape $array */ public static function isArrayShape(array $array): bool; } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Common/Contracts/CachesDataInterface.php������������������������������������������������������������0000644�����������������00000000542�15250554011�0014245 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Common\Contracts; /** * Interface for objects that cache data. * * @since 0.4.0 */ interface CachesDataInterface { /** * Invalidates all caches managed by this object. * * @since 0.4.0 * * @return void */ public function invalidateCaches(): void; } ��������������������������������������������������������������������������������������������������������������������������������������������������������������Common/error_log������������������������������������������������������������������������������������0000644�����������������00000012322�15250554011�0007707 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������[15-Jul-2026 16:16:42 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithArrayTransformationInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php:28 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php on line 28 [24-Jul-2026 12:15:11 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithArrayTransformationInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php:28 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php on line 28 [25-Jul-2026 05:32:59 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithArrayTransformationInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php:28 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php on line 28 [04-Aug-2026 23:27:17 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithArrayTransformationInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php:28 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php on line 28 [13-Aug-2026 01:33:40 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithArrayTransformationInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php:28 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php on line 28 [13-Aug-2026 13:00:10 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithArrayTransformationInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php:28 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php on line 28 [19-Aug-2026 21:32:06 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithArrayTransformationInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php:28 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php on line 28 [19-Aug-2026 22:05:15 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithArrayTransformationInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php:28 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php on line 28 [19-Aug-2026 23:23:18 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithArrayTransformationInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php:28 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php on line 28 [03-Sep-2026 06:47:24 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithArrayTransformationInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php:28 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php on line 28 [03-Sep-2026 23:30:28 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithArrayTransformationInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php:28 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php on line 28 [05-Sep-2026 07:01:31 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithArrayTransformationInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php:28 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php on line 28 [09-Sep-2026 04:06:57 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithArrayTransformationInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php:28 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php on line 28 ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Common/AbstractEnum.php�����������������������������������������������������������������������������0000644�����������������00000026160�15250554011�0011100 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Common; use BadMethodCallException; use JsonSerializable; use ReflectionClass; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Common\Exception\RuntimeException; /** * Abstract base class for enum-like behavior in PHP 7.4. * * This class provides enum-like functionality for PHP versions that don't support native enums. * Child classes should define uppercase snake_case constants for enum values. * * @example * class PersonEnum extends AbstractEnum { * public const FIRST_NAME = 'first'; * public const LAST_NAME = 'last'; * } * * // Usage: * $enum = PersonEnum::from('first'); // Creates instance with value 'first' * $enum = PersonEnum::tryFrom('invalid'); // Returns null * $enum = PersonEnum::firstName(); // Creates instance with value 'first' * $enum->name; // 'FIRST_NAME' * $enum->value; // 'first' * $enum->equals('first'); // Returns true * $enum->is(PersonEnum::firstName()); // Returns true * PersonEnum::cases(); // Returns array of all enum instances * * @property-read string $value The value of the enum instance. * @property-read string $name The name of the enum constant. * * @since 0.1.0 */ abstract class AbstractEnum implements JsonSerializable { /** * @var string The value of the enum instance. */ private string $value; /** * @var string The name of the enum constant. */ private string $name; /** * @var array<string, array<string, string>> Cache for reflection data. */ private static array $cache = []; /** * @var array<string, array<string, self>> Cache for enum instances. */ private static array $instances = []; /** * Constructor is private to ensure instances are created through static methods. * * @since 0.1.0 * * @param string $value The enum value. * @param string $name The constant name. */ final private function __construct(string $value, string $name) { $this->value = $value; $this->name = $name; } /** * Provides read-only access to properties. * * @since 0.1.0 * * @param string $property The property name. * @return mixed The property value. * @throws BadMethodCallException If property doesn't exist. */ final public function __get(string $property) { if ($property === 'value' || $property === 'name') { return $this->{$property}; } throw new BadMethodCallException(sprintf('Property %s::%s does not exist', static::class, $property)); } /** * Prevents property modification. * * @since 0.1.0 * * @param string $property The property name. * @param mixed $value The value to set. * @throws BadMethodCallException Always, as enum properties are read-only. */ final public function __set(string $property, $value): void { throw new BadMethodCallException(sprintf('Cannot modify property %s::%s - enum properties are read-only', static::class, $property)); } /** * Creates an enum instance from a value, throws exception if invalid. * * @since 0.1.0 * * @param string $value The enum value. * @return static The enum instance. * @throws InvalidArgumentException If the value is not valid. */ final public static function from(string $value): self { $instance = self::tryFrom($value); if ($instance === null) { throw new InvalidArgumentException(sprintf('%s is not a valid backing value for enum %s', $value, static::class)); } return $instance; } /** * Tries to create an enum instance from a value, returns null if invalid. * * @since 0.1.0 * * @param string $value The enum value. * @return static|null The enum instance or null. */ final public static function tryFrom(string $value): ?self { $constants = static::getConstants(); foreach ($constants as $name => $constantValue) { if ($constantValue === $value) { return self::getInstance($constantValue, $name); } } return null; } /** * Gets all enum cases. * * @since 0.1.0 * * @return static[] Array of all enum instances. */ final public static function cases(): array { $cases = []; $constants = static::getConstants(); foreach ($constants as $name => $value) { $cases[] = self::getInstance($value, $name); } return $cases; } /** * Checks if this enum has the same value as the given value. * * @since 0.1.0 * * @param string|self $other The value or enum to compare. * @return bool True if values are equal. */ final public function equals($other): bool { if ($other instanceof self) { return $this->is($other); } return $this->value === $other; } /** * Checks if this enum is the same instance type and value as another enum. * * @since 0.1.0 * * @param self $other The other enum to compare. * @return bool True if enums are identical. */ final public function is(self $other): bool { return $this === $other; // Since we're using singletons, we can use identity comparison } /** * Gets all valid values for this enum. * * @since 0.1.0 * * @return string[] List of all enum values. */ final public static function getValues(): array { return array_values(static::getConstants()); } /** * Checks if a value is valid for this enum. * * @since 0.1.0 * * @param string $value The value to check. * @return bool True if value is valid. */ final public static function isValidValue(string $value): bool { return in_array($value, self::getValues(), \true); } /** * Gets or creates a singleton instance for the given value and name. * * @since 0.1.0 * * @param string $value The enum value. * @param string $name The constant name. * @return static The enum instance. */ private static function getInstance(string $value, string $name): self { $className = static::class; if (!isset(self::$instances[$className])) { self::$instances[$className] = []; } if (!isset(self::$instances[$className][$name])) { $instance = new $className($value, $name); self::$instances[$className][$name] = $instance; } /** @var static */ return self::$instances[$className][$name]; } /** * Gets all constants for this enum class. * * @since 0.1.0 * * @return array<string, string> Map of constant names to values. * @throws RuntimeException If invalid constant found. */ final protected static function getConstants(): array { $className = static::class; if (!isset(self::$cache[$className])) { self::$cache[$className] = static::determineClassEnumerations($className); } return self::$cache[$className]; } /** * Determines the class enumerations by reflecting on class constants. * * This method can be overridden by subclasses to customize how * enumerations are determined (e.g., to add dynamic constants). * * @since 0.1.0 * * @param class-string $className The fully qualified class name. * @return array<string, string> Map of constant names to values. * @throws RuntimeException If invalid constant found. */ protected static function determineClassEnumerations(string $className): array { $reflection = new ReflectionClass($className); $constants = $reflection->getConstants(); // Validate all constants $enumConstants = []; foreach ($constants as $name => $value) { // Check if constant name follows uppercase snake_case pattern if (!preg_match('/^[A-Z][A-Z0-9_]*$/', $name)) { throw new RuntimeException(sprintf('Invalid enum constant name "%s" in %s. Constants must be UPPER_SNAKE_CASE.', $name, $className)); } // Check if value is valid type if (!is_string($value)) { throw new RuntimeException(sprintf('Invalid enum value type for constant %s::%s. ' . 'Only string values are allowed, %s given.', $className, $name, gettype($value))); } $enumConstants[$name] = $value; } return $enumConstants; } /** * Handles dynamic method calls for enum checking. * * @since 0.1.0 * * @param string $name The method name. * @param array<mixed> $arguments The method arguments. * @return bool True if the enum value matches. * @throws BadMethodCallException If the method doesn't exist. */ final public function __call(string $name, array $arguments): bool { // Handle is* methods if (str_starts_with($name, 'is')) { $constantName = self::camelCaseToConstant(substr($name, 2)); $constants = static::getConstants(); if (isset($constants[$constantName])) { return $this->value === $constants[$constantName]; } } throw new BadMethodCallException(sprintf('Method %s::%s does not exist', static::class, $name)); } /** * Handles static method calls for enum creation. * * @since 0.1.0 * * @param string $name The method name. * @param array<mixed> $arguments The method arguments. * @return static The enum instance. * @throws BadMethodCallException If the method doesn't exist. */ final public static function __callStatic(string $name, array $arguments): self { $constantName = self::camelCaseToConstant($name); $constants = static::getConstants(); if (isset($constants[$constantName])) { return self::getInstance($constants[$constantName], $constantName); } throw new BadMethodCallException(sprintf('Method %s::%s does not exist', static::class, $name)); } /** * Converts camelCase to CONSTANT_CASE. * * @since 0.1.0 * * @param string $camelCase The camelCase string. * @return string The CONSTANT_CASE version. */ private static function camelCaseToConstant(string $camelCase): string { $snakeCase = preg_replace('/([a-z])([A-Z])/', '$1_$2', $camelCase); if ($snakeCase === null) { return strtoupper($camelCase); } return strtoupper($snakeCase); } /** * Returns string representation of the enum. * * @since 0.1.0 * * @return string The enum value. */ final public function __toString(): string { return $this->value; } /** * Converts the enum to a JSON-serializable format. * * @since 0.1.0 * * @return string The enum value. */ #[\ReturnTypeWillChange] public function jsonSerialize() { return $this->value; } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Common/AbstractDataTransferObject.php���������������������������������������������������������������0000644�����������������00000011175�15250554011�0013701 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Common; use JsonSerializable; use stdClass; use WordPress\AiClient\Common\Contracts\WithArrayTransformationInterface; use WordPress\AiClient\Common\Contracts\WithJsonSchemaInterface; use WordPress\AiClient\Common\Exception\InvalidArgumentException; /** * Abstract base class for all Data Value Objects in the AI Client. * * This abstract class consolidates the common functionality needed by all * data transfer objects: * - Array transformation for data manipulation * - JSON schema support for validation and documentation * - JSON serialization with proper empty object handling * * All DTOs in the AI Client should extend this class to ensure * consistent behavior across the codebase. * * @since 0.1.0 * * @template TArrayShape of array<string, mixed> * @implements WithArrayTransformationInterface<TArrayShape> */ abstract class AbstractDataTransferObject implements WithArrayTransformationInterface, WithJsonSchemaInterface, JsonSerializable { /** * Validates that required keys exist in the array data. * * @since 0.1.0 * * @param array<mixed> $data The array data to validate. * @param string[] $requiredKeys The keys that must be present. * @throws InvalidArgumentException If any required key is missing. */ protected static function validateFromArrayData(array $data, array $requiredKeys): void { $missingKeys = []; foreach ($requiredKeys as $key) { if (!array_key_exists($key, $data)) { $missingKeys[] = $key; } } if (!empty($missingKeys)) { throw new InvalidArgumentException(sprintf('%s::fromArray() missing required keys: %s', static::class, implode(', ', $missingKeys))); } } /** * {@inheritDoc} * * @since 0.1.0 */ public static function isArrayShape(array $array): bool { try { /** @var TArrayShape $array */ static::fromArray($array); return \true; } catch (InvalidArgumentException $e) { return \false; } } /** * Converts the object to a JSON-serializable format. * * This method uses the toArray() method and then processes the result * based on the JSON schema to ensure proper object representation for * empty arrays. * * @since 0.1.0 * * @return mixed The JSON-serializable representation. */ #[\ReturnTypeWillChange] public function jsonSerialize() { $data = $this->toArray(); $schema = static::getJsonSchema(); return $this->convertEmptyArraysToObjects($data, $schema); } /** * Recursively converts empty arrays to stdClass objects where the schema expects objects. * * @since 0.1.0 * * @param mixed $data The data to process. * @param array<mixed, mixed> $schema The JSON schema for the data. * @return mixed The processed data. */ private function convertEmptyArraysToObjects($data, array $schema) { // If data is an empty array and schema expects object, convert to stdClass if (is_array($data) && empty($data) && isset($schema['type']) && $schema['type'] === 'object') { return new stdClass(); } // If data is an array with content, recursively process nested structures if (is_array($data)) { // Handle object properties if (isset($schema['properties']) && is_array($schema['properties'])) { foreach ($data as $key => $value) { if (isset($schema['properties'][$key]) && is_array($schema['properties'][$key])) { $data[$key] = $this->convertEmptyArraysToObjects($value, $schema['properties'][$key]); } } } // Handle array items if (isset($schema['items']) && is_array($schema['items'])) { foreach ($data as $index => $item) { $data[$index] = $this->convertEmptyArraysToObjects($item, $schema['items']); } } // Handle oneOf/anyOf schemas - just use the first one foreach (['oneOf', 'anyOf'] as $keyword) { if (isset($schema[$keyword]) && is_array($schema[$keyword])) { foreach ($schema[$keyword] as $possibleSchema) { if (is_array($possibleSchema)) { return $this->convertEmptyArraysToObjects($data, $possibleSchema); } } } } } return $data; } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Common/Exception/error_log��������������������������������������������������������������������������0000644�����������������00000037252�15250554011�0011656 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������[15-Jul-2026 16:16:44 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php on line 15 [15-Jul-2026 16:16:44 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php on line 15 [15-Jul-2026 16:16:45 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php on line 15 [24-Jul-2026 12:15:13 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php on line 15 [24-Jul-2026 12:15:13 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php on line 15 [24-Jul-2026 12:15:13 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php on line 15 [25-Jul-2026 05:33:02 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php on line 15 [25-Jul-2026 05:33:02 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php on line 15 [25-Jul-2026 05:33:03 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php on line 15 [04-Aug-2026 23:27:19 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php on line 15 [04-Aug-2026 23:27:20 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php on line 15 [04-Aug-2026 23:27:20 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php on line 15 [13-Aug-2026 01:33:41 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php on line 15 [13-Aug-2026 01:33:42 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php on line 15 [13-Aug-2026 01:33:42 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php on line 15 [13-Aug-2026 13:00:11 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php on line 15 [13-Aug-2026 13:00:11 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php on line 15 [13-Aug-2026 13:00:11 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php on line 15 [19-Aug-2026 21:32:06 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php on line 15 [19-Aug-2026 21:32:06 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php on line 15 [19-Aug-2026 21:32:07 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php on line 15 [19-Aug-2026 22:05:18 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php on line 15 [19-Aug-2026 22:05:18 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php on line 15 [19-Aug-2026 22:05:18 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php on line 15 [19-Aug-2026 23:23:23 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php on line 15 [19-Aug-2026 23:23:24 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php on line 15 [19-Aug-2026 23:23:24 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php on line 15 [03-Sep-2026 06:50:36 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php on line 15 [03-Sep-2026 06:50:51 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php on line 15 [03-Sep-2026 06:51:38 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php on line 15 [05-Sep-2026 13:05:49 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php on line 15 [05-Sep-2026 22:12:48 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php on line 15 [05-Sep-2026 23:57:39 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php on line 15 [06-Sep-2026 14:52:34 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php on line 15 [06-Sep-2026 23:54:11 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php on line 15 [07-Sep-2026 01:52:42 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php on line 15 [09-Sep-2026 00:44:43 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php on line 15 [09-Sep-2026 00:44:44 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php on line 15 [09-Sep-2026 00:44:45 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php:15 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php on line 15 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Common/Exception/InvalidArgumentException.php�������������������������������������������������������0000644�����������������00000000747�15250554011�0015421 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Common\Exception; use WordPress\AiClient\Common\Contracts\AiClientExceptionInterface; /** * Exception thrown when an invalid argument is provided. * * This extends PHP's built-in InvalidArgumentException while implementing * the AI Client exception interface for consistent catch handling. * * @since 0.2.0 */ class InvalidArgumentException extends \InvalidArgumentException implements AiClientExceptionInterface { } �������������������������Common/Exception/TokenLimitReachedException.php�����������������������������������������������������0000644�����������������00000002616�15250554011�0015660 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Common\Exception; /** * Exception thrown when a token limit is reached during prompt fulfillment. * * Providers should throw this exception when the token usage for a request * exceeds the allowed limit, whether that is the model's context window * or a configured maximum. * * @since 1.0.0 */ class TokenLimitReachedException extends \WordPress\AiClient\Common\Exception\RuntimeException { /** * The token limit that was reached, if known. * * @since 1.0.0 * * @var int|null */ private $maxTokens; /** * Creates a new TokenLimitReachedException. * * @since 1.0.0 * * @param string $message The exception message. * @param int|null $maxTokens The token limit that was reached, if known. * @param \Throwable|null $previous The previous throwable used for exception chaining. */ public function __construct(string $message = '', ?int $maxTokens = null, ?\Throwable $previous = null) { parent::__construct($message, 0, $previous); $this->maxTokens = $maxTokens; } /** * Returns the token limit that was reached, if known. * * @since 1.0.0 * * @return int|null The token limit, or null if not provided. */ public function getMaxTokens(): ?int { return $this->maxTokens; } } ������������������������������������������������������������������������������������������������������������������Common/Exception/RuntimeException.php���������������������������������������������������������������0000644�����������������00000000675�15250554011�0013753 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Common\Exception; use WordPress\AiClient\Common\Contracts\AiClientExceptionInterface; /** * Exception thrown for runtime errors. * * This extends PHP's built-in RuntimeException while implementing * the AI Client exception interface for consistent catch handling. * * @since 0.2.0 */ class RuntimeException extends \RuntimeException implements AiClientExceptionInterface { } �������������������������������������������������������������������Common/Traits/WithDataCachingTrait.php��������������������������������������������������������������0000644�����������������00000011762�15250554011�0013746 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Common\Traits; use WordPress\AiClient\AiClient; /** * Trait for objects that cache data using PSR-16 cache with in-memory fallback. * * When a PSR-16 cache is configured via AiClient::setCache(), data is stored persistently. * Otherwise, data is cached in-memory for the duration of the request. * * @since 0.4.0 */ trait WithDataCachingTrait { /** * In-memory cache used when no PSR-16 cache is configured. * * @since 0.4.0 * * @var array<string, mixed> */ private array $localCache = []; /** * Gets the cache key suffixes managed by this object. * * @since 0.4.0 * * @return list<string> The cache key suffixes. */ abstract protected function getCachedKeys(): array; /** * Gets the base cache key for this object. * * The base cache key is used as a prefix for all cache keys managed by this object. * It should be unique to the implementing class to avoid cache key collisions. * * @since 0.4.0 * * @return string The base cache key. */ abstract protected function getBaseCacheKey(): string; /** * Checks if a value exists in the cache. * * @since 0.4.0 * * @param string $key The cache key suffix (will be appended to the base key). * @return bool True if the value exists in cache, false otherwise. */ protected function hasCache(string $key): bool { $fullKey = $this->buildCacheKey($key); $cache = AiClient::getCache(); if ($cache !== null) { return $cache->has($fullKey); } return array_key_exists($fullKey, $this->localCache); } /** * Gets a value from the cache, or computes and caches it if not present. * * @since 0.4.0 * * @param string $key The cache key suffix (will be appended to the base key). * @param callable $callback The callback to compute the value if not cached. * @param int|\DateInterval|null $ttl The TTL for the cache entry, or null for default. * Ignored for local cache. * @return mixed The cached or computed value. */ protected function cached(string $key, callable $callback, $ttl = null) { if ($this->hasCache($key)) { return $this->getCache($key); } $value = $callback(); $this->setCache($key, $value, $ttl); return $value; } /** * Gets a value from the cache. * * @since 0.4.0 * * @param string $key The cache key suffix (will be appended to the base key). * @param mixed $default The default value to return if the key does not exist. * @return mixed The cached value or the default value if not found. */ protected function getCache(string $key, $default = null) { $fullKey = $this->buildCacheKey($key); $cache = AiClient::getCache(); if ($cache !== null) { return $cache->get($fullKey, $default); } return $this->localCache[$fullKey] ?? $default; } /** * Sets a value in the cache. * * @since 0.4.0 * * @param string $key The cache key suffix (will be appended to the base key). * @param mixed $value The value to cache. * @param int|\DateInterval|null $ttl The TTL for the cache entry, or null for default. Ignored for local cache. * @return bool True on success, false on failure. */ protected function setCache(string $key, $value, $ttl = null): bool { $fullKey = $this->buildCacheKey($key); $cache = AiClient::getCache(); if ($cache !== null) { return $cache->set($fullKey, $value, $ttl); } $this->localCache[$fullKey] = $value; return \true; } /** * Invalidates all caches managed by this object. * * @since 0.4.0 * * @return void */ public function invalidateCaches(): void { foreach ($this->getCachedKeys() as $key) { $this->clearCache($key); } } /** * Clears a value from the cache. * * @since 0.4.0 * * @param string $key The cache key suffix (will be appended to the base key). * @return bool True on success, false on failure. */ protected function clearCache(string $key): bool { $fullKey = $this->buildCacheKey($key); $cache = AiClient::getCache(); if ($cache !== null) { return $cache->delete($fullKey); } unset($this->localCache[$fullKey]); return \true; } /** * Builds the full cache key by combining the base key with the suffix. * * @since 0.4.0 * * @param string $key The cache key suffix. * @return string The full cache key. */ private function buildCacheKey(string $key): string { return $this->getBaseCacheKey() . '_' . $key; } } ��������������Files/DTO/File.php����������������������������������������������������������������������������������0000644�����������������00000032326�15250554011�0007630 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Files\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Common\Exception\RuntimeException; use WordPress\AiClient\Files\Enums\FileTypeEnum; use WordPress\AiClient\Files\ValueObjects\MimeType; /** * Represents a file in the AI client. * * This DTO automatically detects whether a file is a URL, base64 data, or local file path * and handles them appropriately. * * @since 0.1.0 * * @phpstan-type FileArrayShape array{ * fileType: string, * url?: string, * mimeType: string, * base64Data?: string * } * * @extends AbstractDataTransferObject<FileArrayShape> */ class File extends AbstractDataTransferObject { public const KEY_FILE_TYPE = 'fileType'; public const KEY_MIME_TYPE = 'mimeType'; public const KEY_URL = 'url'; public const KEY_BASE64_DATA = 'base64Data'; /** * @var MimeType The MIME type of the file. */ private MimeType $mimeType; /** * @var FileTypeEnum The type of file storage. */ private FileTypeEnum $fileType; /** * @var string|null The URL for remote files. */ private ?string $url = null; /** * @var string|null The base64 data for inline files. */ private ?string $base64Data = null; /** * Constructor. * * @since 0.1.0 * * @param string $file The file string (URL, base64 data, or local path). * @param string|null $mimeType The MIME type of the file (optional). * @throws InvalidArgumentException If the file format is invalid or MIME type cannot be determined. */ public function __construct(string $file, ?string $mimeType = null) { // Detect and process the file type (will set MIME type if possible) $this->detectAndProcessFile($file, $mimeType); } /** * Detects the file type and processes it accordingly. * * @since 0.1.0 * * @param string $file The file string to process. * @param string|null $providedMimeType The explicitly provided MIME type. * @throws InvalidArgumentException If the file format is invalid or MIME type cannot be determined. */ private function detectAndProcessFile(string $file, ?string $providedMimeType): void { // Check if it's a URL if ($this->isUrl($file)) { $this->fileType = FileTypeEnum::remote(); $this->url = $file; $this->mimeType = $this->determineMimeType($providedMimeType, null, $file); return; } // Data URI pattern. $dataUriPattern = '/^data:(?:([a-zA-Z0-9][a-zA-Z0-9!#$&\-\^_+.]*\/[a-zA-Z0-9][a-zA-Z0-9!#$&\-\^_+.]*' . '(?:;[a-zA-Z0-9\-]+=[a-zA-Z0-9\-]+)*)?;)?base64,([A-Za-z0-9+\/]*={0,2})$/'; // Check if it's a data URI. if (preg_match($dataUriPattern, $file, $matches)) { $this->fileType = FileTypeEnum::inline(); $this->base64Data = $matches[2]; // Extract just the base64 data $extractedMimeType = empty($matches[1]) ? null : $matches[1]; $this->mimeType = $this->determineMimeType($providedMimeType, $extractedMimeType, null); return; } // Check if it's a local file path (before base64 check) if (file_exists($file) && is_file($file)) { $this->fileType = FileTypeEnum::inline(); $this->base64Data = $this->convertFileToBase64($file); $this->mimeType = $this->determineMimeType($providedMimeType, null, $file); return; } // Check if it's plain base64 if (preg_match('/^[A-Za-z0-9+\/]*={0,2}$/', $file)) { if ($providedMimeType === null) { throw new InvalidArgumentException('MIME type is required when providing plain base64 data without data URI format.'); } $this->fileType = FileTypeEnum::inline(); $this->base64Data = $file; $this->mimeType = new MimeType($providedMimeType); return; } throw new InvalidArgumentException('Invalid file provided. Expected URL, base64 data, or valid local file path.'); } /** * Checks if a string is a valid URL. * * @since 0.1.0 * * @param string $string The string to check. * @return bool True if the string is a URL. */ private function isUrl(string $string): bool { return filter_var($string, \FILTER_VALIDATE_URL) !== \false && preg_match('/^https?:\/\//i', $string); } /** * Converts a local file to base64. * * @since 0.1.0 * * @param string $filePath The path to the local file. * @return string The base64-encoded file data. * @throws RuntimeException If the file cannot be read. */ private function convertFileToBase64(string $filePath): string { $fileContent = @file_get_contents($filePath); if ($fileContent === \false) { throw new RuntimeException(sprintf('Unable to read file: %s', $filePath)); } return base64_encode($fileContent); } /** * Gets the file type. * * @since 0.1.0 * * @return FileTypeEnum The file type. */ public function getFileType(): FileTypeEnum { return $this->fileType; } /** * Checks if the file is an inline file. * * @since 0.1.0 * * @return bool True if the file is inline (base64/data URI). */ public function isInline(): bool { return $this->fileType->isInline(); } /** * Checks if the file is a remote file. * * @since 0.1.0 * * @return bool True if the file is remote (URL). */ public function isRemote(): bool { return $this->fileType->isRemote(); } /** * Gets the URL for remote files. * * @since 0.1.0 * * @return string|null The URL, or null if not a remote file. */ public function getUrl(): ?string { return $this->url; } /** * Gets the base64-encoded data for inline files. * * @since 0.1.0 * * @return string|null The plain base64-encoded data (without data URI prefix), or null if not an inline file. */ public function getBase64Data(): ?string { return $this->base64Data; } /** * Gets the data as a data URI for inline files. * * @since 0.1.0 * * @return string|null The data URI in format: data:[mimeType];base64,[data], or null if not an inline file. */ public function getDataUri(): ?string { if ($this->base64Data === null) { return null; } return sprintf('data:%s;base64,%s', $this->getMimeType(), $this->base64Data); } /** * Gets the MIME type of the file as a string. * * @since 0.1.0 * * @return string The MIME type string value. */ public function getMimeType(): string { return (string) $this->mimeType; } /** * Gets the MIME type object. * * @since 0.1.0 * * @return MimeType The MIME type object. */ public function getMimeTypeObject(): MimeType { return $this->mimeType; } /** * Checks if the file is a video. * * @since 0.1.0 * * @return bool True if the file is a video. */ public function isVideo(): bool { return $this->mimeType->isVideo(); } /** * Checks if the file is an image. * * @since 0.1.0 * * @return bool True if the file is an image. */ public function isImage(): bool { return $this->mimeType->isImage(); } /** * Checks if the file is audio. * * @since 0.1.0 * * @return bool True if the file is audio. */ public function isAudio(): bool { return $this->mimeType->isAudio(); } /** * Checks if the file is text. * * @since 0.1.0 * * @return bool True if the file is text. */ public function isText(): bool { return $this->mimeType->isText(); } /** * Checks if the file is a document. * * @since 0.1.0 * * @return bool True if the file is a document. */ public function isDocument(): bool { return $this->mimeType->isDocument(); } /** * Checks if the file is a specific MIME type. * * @since 0.1.0 * * @param string $type The mime type to check (e.g. 'image', 'text', 'video', 'audio'). * * @return bool True if the file is of the specified type. */ public function isMimeType(string $type): bool { return $this->mimeType->isType($type); } /** * Determines the MIME type from various sources. * * @since 0.1.0 * * @param string|null $providedMimeType The explicitly provided MIME type. * @param string|null $extractedMimeType The MIME type extracted from data URI. * @param string|null $pathOrUrl The file path or URL to extract extension from. * @return MimeType The determined MIME type. * @throws InvalidArgumentException If MIME type cannot be determined. */ private function determineMimeType(?string $providedMimeType, ?string $extractedMimeType, ?string $pathOrUrl): MimeType { // Prefer explicitly provided MIME type if ($providedMimeType !== null) { return new MimeType($providedMimeType); } // Use extracted MIME type from data URI if ($extractedMimeType !== null) { return new MimeType($extractedMimeType); } // Try to determine from file extension if ($pathOrUrl !== null) { $parsedUrl = parse_url($pathOrUrl); $path = $parsedUrl['path'] ?? $pathOrUrl; // Remove query string and fragment if present $cleanPath = strtok($path, '?#'); if ($cleanPath === \false) { $cleanPath = $path; } $extension = pathinfo($cleanPath, \PATHINFO_EXTENSION); if (!empty($extension)) { try { return MimeType::fromExtension($extension); } catch (InvalidArgumentException $e) { // Extension not recognized, continue to error unset($e); } } } throw new InvalidArgumentException('Unable to determine MIME type. Please provide it explicitly.'); } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'oneOf' => [['properties' => [self::KEY_FILE_TYPE => ['type' => 'string', 'const' => FileTypeEnum::REMOTE, 'description' => 'The file type.'], self::KEY_MIME_TYPE => ['type' => 'string', 'description' => 'The MIME type of the file.', 'pattern' => '^[a-zA-Z0-9][a-zA-Z0-9!#$&\-\^_+.]*\/[a-zA-Z0-9]' . '[a-zA-Z0-9!#$&\-\^_+.]*$'], self::KEY_URL => ['type' => 'string', 'format' => 'uri', 'description' => 'The URL to the remote file.']], 'required' => [self::KEY_FILE_TYPE, self::KEY_MIME_TYPE, self::KEY_URL]], ['properties' => [self::KEY_FILE_TYPE => ['type' => 'string', 'const' => FileTypeEnum::INLINE, 'description' => 'The file type.'], self::KEY_MIME_TYPE => ['type' => 'string', 'description' => 'The MIME type of the file.', 'pattern' => '^[a-zA-Z0-9][a-zA-Z0-9!#$&\-\^_+.]*\/[a-zA-Z0-9]' . '[a-zA-Z0-9!#$&\-\^_+.]*$'], self::KEY_BASE64_DATA => ['type' => 'string', 'description' => 'The base64-encoded file data.']], 'required' => [self::KEY_FILE_TYPE, self::KEY_MIME_TYPE, self::KEY_BASE64_DATA]]]]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return FileArrayShape */ public function toArray(): array { $data = [self::KEY_FILE_TYPE => $this->fileType->value, self::KEY_MIME_TYPE => $this->getMimeType()]; if ($this->url !== null) { $data[self::KEY_URL] = $this->url; } elseif (!$this->fileType->isRemote() && $this->base64Data !== null) { $data[self::KEY_BASE64_DATA] = $this->base64Data; } else { throw new RuntimeException('File requires either url or base64Data. This should not be a possible condition.'); } return $data; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function fromArray(array $array): self { static::validateFromArrayData($array, [self::KEY_FILE_TYPE]); // Check which properties are set to determine how to construct the File $mimeType = $array[self::KEY_MIME_TYPE] ?? null; if (isset($array[self::KEY_URL])) { return new self($array[self::KEY_URL], $mimeType); } elseif (isset($array[self::KEY_BASE64_DATA])) { return new self($array[self::KEY_BASE64_DATA], $mimeType); } else { throw new InvalidArgumentException('File requires either url or base64Data.'); } } /** * Performs a deep clone of the file. * * This method ensures that the MimeType value object is cloned to prevent * any shared references between the original and cloned file. * * @since 0.4.2 */ public function __clone() { $this->mimeType = clone $this->mimeType; } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Files/DTO/error_log���������������������������������������������������������������������������������0000644�����������������00000010200�15250554011�0010140 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������[15-Jul-2026 16:16:32 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/DTO/File.php:28 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/DTO/File.php on line 28 [24-Jul-2026 12:15:17 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/DTO/File.php:28 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/DTO/File.php on line 28 [25-Jul-2026 05:33:06 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/DTO/File.php:28 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/DTO/File.php on line 28 [04-Aug-2026 23:28:00 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/DTO/File.php:28 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/DTO/File.php on line 28 [13-Aug-2026 01:32:54 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/DTO/File.php:28 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/DTO/File.php on line 28 [13-Aug-2026 12:59:34 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/DTO/File.php:28 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/DTO/File.php on line 28 [19-Aug-2026 21:32:05 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/DTO/File.php:28 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/DTO/File.php on line 28 [19-Aug-2026 22:06:32 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/DTO/File.php:28 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/DTO/File.php on line 28 [19-Aug-2026 23:24:33 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/DTO/File.php:28 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/DTO/File.php on line 28 [04-Sep-2026 16:54:18 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/DTO/File.php:28 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/DTO/File.php on line 28 [05-Sep-2026 19:44:26 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/DTO/File.php:28 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/DTO/File.php on line 28 [09-Sep-2026 04:06:58 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/DTO/File.php:28 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/DTO/File.php on line 28 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Files/Enums/FileTypeEnum.php������������������������������������������������������������������������0000644�����������������00000001330�15250554011�0011747 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Files\Enums; use WordPress\AiClient\Common\AbstractEnum; /** * Represents the type of file storage. * * @method static self inline() Returns the inline file type. * @method static self remote() Returns the remote file type. * @method bool isInline() Checks if this is an inline file type. * @method bool isRemote() Checks if this is a remote file type. * * @since 0.1.0 */ class FileTypeEnum extends AbstractEnum { /** * Inline file with base64-encoded data. * * @var string */ public const INLINE = 'inline'; /** * Remote file referenced by URL. * * @var string */ public const REMOTE = 'remote'; } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Files/Enums/error_log�������������������������������������������������������������������������������0000644�����������������00000022454�15250554011�0010617 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������[15-Jul-2026 16:16:33 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php on line 17 [15-Jul-2026 16:16:33 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php on line 19 [24-Jul-2026 12:15:18 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php on line 19 [24-Jul-2026 12:15:18 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php on line 17 [25-Jul-2026 05:33:04 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php on line 17 [25-Jul-2026 05:33:04 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php on line 19 [04-Aug-2026 23:27:59 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php on line 17 [04-Aug-2026 23:27:59 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php on line 19 [13-Aug-2026 01:32:52 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php on line 19 [13-Aug-2026 01:32:52 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php on line 17 [13-Aug-2026 12:59:37 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php on line 17 [13-Aug-2026 12:59:37 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php on line 19 [19-Aug-2026 21:32:05 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php on line 17 [19-Aug-2026 21:32:05 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php on line 19 [19-Aug-2026 22:06:27 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php on line 17 [19-Aug-2026 22:06:27 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php on line 19 [19-Aug-2026 23:24:34 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php on line 17 [19-Aug-2026 23:24:34 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php on line 19 [03-Sep-2026 06:49:45 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php on line 19 [03-Sep-2026 06:56:44 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php on line 17 [03-Sep-2026 22:46:29 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php on line 19 [03-Sep-2026 23:30:38 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php on line 17 [05-Sep-2026 07:01:51 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php on line 17 [05-Sep-2026 09:42:34 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php on line 19 [08-Sep-2026 18:15:51 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php on line 19 [08-Sep-2026 18:56:19 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php on line 17 ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Files/Enums/MediaOrientationEnum.php����������������������������������������������������������������0000644�����������������00000001730�15250554011�0013465 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Files\Enums; use WordPress\AiClient\Common\AbstractEnum; /** * Represents the type of file storage. * * @method static self square() Returns the square orientation * @method static self landscape() Returns the landscape orientation. * @method static self portrait() Returns the portrait orientation. * @method bool isSquare() Checks if this is an square orientation * @method bool isLandscape() Checks if this is a landscape orientation. * @method bool isPortrait() Checks if this is a portrait orientation. * * @since 0.1.0 */ class MediaOrientationEnum extends AbstractEnum { /** * Square orientation. * * @var string */ public const SQUARE = 'square'; /** * Landscape orientation. * * @var string */ public const LANDSCAPE = 'landscape'; /** * Portrait orientation. * * @var string */ public const PORTRAIT = 'portrait'; } ����������������������������������������Files/ValueObjects/MimeType.php���������������������������������������������������������������������0000644�����������������00000017554�15250554011�0012450 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Files\ValueObjects; use WordPress\AiClient\Common\Exception\InvalidArgumentException; /** * Value object representing a MIME type. * * This immutable value object encapsulates MIME type validation and * provides convenient methods for checking MIME type categories. * * @since 0.1.0 */ final class MimeType { /** * @var string The MIME type value. */ private string $value; /** * Common MIME type mappings for file extensions. * * @var array<string, string> */ private static array $extensionMap = [ // Text 'txt' => 'text/plain', 'html' => 'text/html', 'htm' => 'text/html', 'css' => 'text/css', 'js' => 'application/javascript', 'json' => 'application/json', 'xml' => 'application/xml', 'csv' => 'text/csv', 'md' => 'text/markdown', // Images 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'png' => 'image/png', 'gif' => 'image/gif', 'bmp' => 'image/bmp', 'webp' => 'image/webp', 'svg' => 'image/svg+xml', 'ico' => 'image/x-icon', // Documents 'pdf' => 'application/pdf', 'doc' => 'application/msword', 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'xls' => 'application/vnd.ms-excel', 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'ppt' => 'application/vnd.ms-powerpoint', 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'odt' => 'application/vnd.oasis.opendocument.text', 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', // Archives 'zip' => 'application/zip', 'tar' => 'application/x-tar', 'gz' => 'application/gzip', 'rar' => 'application/x-rar-compressed', '7z' => 'application/x-7z-compressed', // Audio 'mp3' => 'audio/mpeg', 'wav' => 'audio/wav', 'ogg' => 'audio/ogg', 'flac' => 'audio/flac', 'm4a' => 'audio/m4a', 'aac' => 'audio/aac', // Video 'mp4' => 'video/mp4', 'avi' => 'video/x-msvideo', 'mov' => 'video/quicktime', 'wmv' => 'video/x-ms-wmv', 'flv' => 'video/x-flv', 'webm' => 'video/webm', 'mkv' => 'video/x-matroska', // Fonts 'ttf' => 'font/ttf', 'otf' => 'font/otf', 'woff' => 'font/woff', 'woff2' => 'font/woff2', // Other 'php' => 'application/x-httpd-php', 'sh' => 'application/x-sh', 'exe' => 'application/x-msdownload', ]; /** * Document MIME types. * * @var array<string> */ private static array $documentTypes = ['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/vnd.oasis.opendocument.text', 'application/vnd.oasis.opendocument.spreadsheet']; /** * Constructor. * * @since 0.1.0 * * @param string $value The MIME type value. * @throws InvalidArgumentException If the MIME type is invalid. */ public function __construct(string $value) { if (!self::isValid($value)) { throw new InvalidArgumentException(sprintf('Invalid MIME type: %s', $value)); } $this->value = strtolower($value); } /** * Gets the primary known file extension for this MIME type. * * @since 0.1.0 * * @return string The file extension (without the dot). * @throws InvalidArgumentException If no known extension exists for this MIME type. */ public function toExtension(): string { // Reverse lookup for the MIME type to find the extension. $extension = array_search($this->value, self::$extensionMap, \true); if ($extension === \false) { throw new InvalidArgumentException(sprintf('No known extension for MIME type: %s', $this->value)); } return $extension; } /** * Creates a MimeType from a file extension. * * @since 0.1.0 * * @param string $extension The file extension (without the dot). * @return self The MimeType instance. * @throws InvalidArgumentException If the extension is not recognized. */ public static function fromExtension(string $extension): self { $extension = strtolower($extension); if (!isset(self::$extensionMap[$extension])) { throw new InvalidArgumentException(sprintf('Unknown file extension: %s', $extension)); } return new self(self::$extensionMap[$extension]); } /** * Checks if a MIME type string is valid. * * @since 0.1.0 * * @param string $mimeType The MIME type to validate. * @return bool True if valid. */ public static function isValid(string $mimeType): bool { // Basic MIME type validation: type/subtype return (bool) preg_match('/^[a-zA-Z0-9][a-zA-Z0-9!#$&\-\^_+.]*\/[a-zA-Z0-9][a-zA-Z0-9!#$&\-\^_+.]*$/', $mimeType); } /** * Checks if this MIME type is a specific type. * * This method returns true when the stored MIME type begins with the * given prefix. For example, `"audio"` matches `"audio/mpeg"`. * * @since 0.1.0 * * @param string $mimeType The MIME type prefix to check (e.g., "audio", "image"). * @return bool True if this MIME type is of the specified type. */ public function isType(string $mimeType): bool { return str_starts_with($this->value, strtolower($mimeType) . '/'); } /** * Checks if this is an image MIME type. * * @since 0.1.0 * * @return bool True if this is an image type. */ public function isImage(): bool { return $this->isType('image'); } /** * Checks if this is an audio MIME type. * * @since 0.1.0 * * @return bool True if this is an audio type. */ public function isAudio(): bool { return $this->isType('audio'); } /** * Checks if this is a video MIME type. * * @since 0.1.0 * * @return bool True if this is a video type. */ public function isVideo(): bool { return $this->isType('video'); } /** * Checks if this is a text MIME type. * * @since 0.1.0 * * @return bool True if this is a text type. */ public function isText(): bool { return $this->isType('text'); } /** * Checks if this is a document MIME type. * * @since 0.1.0 * * @return bool True if this is a document type. */ public function isDocument(): bool { return in_array($this->value, self::$documentTypes, \true); } /** * Checks if this MIME type equals another. * * @since 0.1.0 * * @param self|string $other The other MIME type to compare. * @return bool True if equal. * @throws InvalidArgumentException If the other MIME type is invalid. */ public function equals($other): bool { if ($other instanceof self) { return $this->value === $other->value; } if (is_string($other)) { return $this->value === strtolower($other); } throw new InvalidArgumentException(sprintf('Invalid MIME type comparison: %s', gettype($other))); } /** * Gets the string representation of the MIME type. * * @since 0.1.0 * * @return string The MIME type value. */ public function __toString(): string { return $this->value; } } ����������������������������������������������������������������������������������������������������������������������������������������������������AiClient.php����������������������������������������������������������������������������������������0000644�����������������00000041720�15250554011�0006747 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient; use WordPress\AiClientDependencies\Psr\EventDispatcher\EventDispatcherInterface; use WordPress\AiClientDependencies\Psr\SimpleCache\CacheInterface; use WordPress\AiClient\Builders\PromptBuilder; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Common\Exception\RuntimeException; use WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface; use WordPress\AiClient\Providers\Contracts\ProviderInterface; use WordPress\AiClient\Providers\Models\Contracts\ModelInterface; use WordPress\AiClient\Providers\Models\DTO\ModelConfig; use WordPress\AiClient\Providers\ProviderRegistry; use WordPress\AiClient\Results\DTO\GenerativeAiResult; /** * Main AI Client class providing both fluent and traditional APIs for AI operations. * * This class serves as the primary entry point for AI operations, offering: * - Fluent API for easy-to-read chained method calls * - Traditional API for array-based configuration (WordPress style) * - Integration with provider registry for model discovery * - Support for three model specification approaches * * All model requirements analysis and capability matching is handled * automatically by the PromptBuilder, which provides intelligent model * discovery based on prompt content and configuration. * * ## Model Specification Approaches * * ### 1. Specific Model Instance * Use a specific ModelInterface instance when you know exactly which model to use: * ```php * $model = $registry->getProvider('openai')->getModel('gpt-4'); * $result = AiClient::generateTextResult('What is PHP?', $model); * ``` * * ### 2. ModelConfig for Auto-Discovery * Use ModelConfig to specify requirements and let the system discover the best model: * ```php * $config = new ModelConfig(); * $config->setTemperature(0.7); * $config->setMaxTokens(150); * * $result = AiClient::generateTextResult('What is PHP?', $config); * ``` * * ### 3. Automatic Discovery (Default) * Pass null or omit the parameter for intelligent model discovery based on prompt content: * ```php * // System analyzes prompt and selects appropriate model automatically * $result = AiClient::generateTextResult('What is PHP?'); * $imageResult = AiClient::generateImageResult('A sunset over mountains'); * ``` * * ## Fluent API Examples * ```php * // Fluent API with automatic model discovery * $result = AiClient::prompt('Generate an image of a sunset') * ->usingTemperature(0.7) * ->generateImageResult(); * * // Fluent API with specific model * $result = AiClient::prompt('What is PHP?') * ->usingModel($specificModel) * ->usingTemperature(0.5) * ->generateTextResult(); * * // Fluent API with model configuration * $result = AiClient::prompt('Explain quantum physics') * ->usingModelConfig($config) * ->generateTextResult(); * ``` * * @since 0.1.0 * * @phpstan-import-type Prompt from PromptBuilder * * phpcs:ignore Generic.Files.LineLength.TooLong */ class AiClient { /** * @var string The version of the AI Client. */ public const VERSION = '1.3.1'; /** * @var ProviderRegistry|null The default provider registry instance. */ private static ?ProviderRegistry $defaultRegistry = null; /** * @var EventDispatcherInterface|null The event dispatcher for prompt lifecycle events. */ private static ?EventDispatcherInterface $eventDispatcher = null; /** * @var CacheInterface|null The PSR-16 cache for storing and retrieving cached data. */ private static ?CacheInterface $cache = null; /** * Gets the default provider registry instance. * * @since 0.1.0 * * @return ProviderRegistry The default provider registry. */ public static function defaultRegistry(): ProviderRegistry { if (self::$defaultRegistry === null) { self::$defaultRegistry = new ProviderRegistry(); } return self::$defaultRegistry; } /** * Sets the event dispatcher for prompt lifecycle events. * * The event dispatcher will be used to dispatch BeforeGenerateResultEvent and * AfterGenerateResultEvent during prompt generation. * * @since 0.4.0 * * @param EventDispatcherInterface|null $dispatcher The event dispatcher, or null to disable. * @return void */ public static function setEventDispatcher(?EventDispatcherInterface $dispatcher): void { self::$eventDispatcher = $dispatcher; } /** * Gets the event dispatcher for prompt lifecycle events. * * @since 0.4.0 * * @return EventDispatcherInterface|null The event dispatcher, or null if not set. */ public static function getEventDispatcher(): ?EventDispatcherInterface { return self::$eventDispatcher; } /** * Sets the PSR-16 cache for storing and retrieving cached data. * * The cache can be used to store AI responses and other data to avoid * redundant API calls and improve performance. * * @since 0.4.0 * * @param CacheInterface|null $cache The PSR-16 cache instance, or null to disable caching. * @return void */ public static function setCache(?CacheInterface $cache): void { self::$cache = $cache; } /** * Gets the PSR-16 cache instance. * * @since 0.4.0 * * @return CacheInterface|null The cache instance, or null if not set. */ public static function getCache(): ?CacheInterface { return self::$cache; } /** * Checks if a provider is configured and available for use. * * Supports multiple input formats for developer convenience: * - ProviderAvailabilityInterface: Direct availability check * - string (provider ID): e.g., AiClient::isConfigured('openai') * - string (class name): e.g., AiClient::isConfigured(OpenAiProvider::class) * * When using string input, this method leverages the ProviderRegistry's centralized * dependency management, ensuring HttpTransporter and authentication are properly * injected into availability instances. * * @since 0.1.0 * @since 0.2.0 Now supports being passed a provider ID or class name. * * @param ProviderAvailabilityInterface|string|class-string<ProviderInterface> $availabilityOrIdOrClassName * The provider availability instance, provider ID, or provider class name. * @return bool True if the provider is configured and available, false otherwise. */ public static function isConfigured($availabilityOrIdOrClassName): bool { // Handle direct ProviderAvailabilityInterface (backward compatibility) if ($availabilityOrIdOrClassName instanceof ProviderAvailabilityInterface) { return $availabilityOrIdOrClassName->isConfigured(); } // Handle string input (provider ID or class name) via registry if (is_string($availabilityOrIdOrClassName)) { return self::defaultRegistry()->isProviderConfigured($availabilityOrIdOrClassName); } throw new \InvalidArgumentException('Parameter must be a ProviderAvailabilityInterface instance, provider ID string, or provider class name. ' . sprintf('Received: %s', is_object($availabilityOrIdOrClassName) ? get_class($availabilityOrIdOrClassName) : gettype($availabilityOrIdOrClassName))); } /** * Creates a new prompt builder for fluent API usage. * * Returns a PromptBuilder instance configured with the specified or default registry. * The traditional API methods in this class delegate to PromptBuilder * for all generation logic. * * @since 0.1.0 * * @param Prompt $prompt Optional initial prompt content. * @param ProviderRegistry|null $registry Optional custom registry. If null, uses default. * @return PromptBuilder The prompt builder instance. */ public static function prompt($prompt = null, ?ProviderRegistry $registry = null): PromptBuilder { return new PromptBuilder($registry ?? self::defaultRegistry(), $prompt, self::$eventDispatcher); } /** * Generates content using a unified API that automatically detects model capabilities. * * When no model is provided, this method delegates to PromptBuilder for intelligent * model discovery based on prompt content and configuration. When a model is provided, * it infers the capability from the model's interfaces and delegates to the capability-based method. * * @since 0.1.0 * * @param Prompt $prompt The prompt content. * @param ModelInterface|ModelConfig $modelOrConfig Specific model to use, or model configuration * for auto-discovery. * @param ProviderRegistry|null $registry Optional custom registry. If null, uses default. * @return GenerativeAiResult The generation result. * * @throws \InvalidArgumentException If the provided model doesn't support any known generation type. * @throws \RuntimeException If no suitable model can be found for the prompt. */ public static function generateResult($prompt, $modelOrConfig, ?ProviderRegistry $registry = null): GenerativeAiResult { self::validateModelOrConfigParameter($modelOrConfig); return self::getConfiguredPromptBuilder($prompt, $modelOrConfig, $registry)->generateResult(); } /** * Generates text using the traditional API approach. * * @since 0.1.0 * * @param Prompt $prompt The prompt content. * @param ModelInterface|ModelConfig|null $modelOrConfig Optional specific model to use, * or model configuration for auto-discovery, * or null for defaults. * @param ProviderRegistry|null $registry Optional custom registry. If null, uses default. * @return GenerativeAiResult The generation result. * * @throws \InvalidArgumentException If the prompt format is invalid. * @throws \RuntimeException If no suitable model is found. */ public static function generateTextResult($prompt, $modelOrConfig = null, ?ProviderRegistry $registry = null): GenerativeAiResult { self::validateModelOrConfigParameter($modelOrConfig); return self::getConfiguredPromptBuilder($prompt, $modelOrConfig, $registry)->generateTextResult(); } /** * Generates an image using the traditional API approach. * * @since 0.1.0 * * @param Prompt $prompt The prompt content. * @param ModelInterface|ModelConfig|null $modelOrConfig Optional specific model to use, * or model configuration for auto-discovery, * or null for defaults. * @param ProviderRegistry|null $registry Optional custom registry. If null, uses default. * @return GenerativeAiResult The generation result. * * @throws \InvalidArgumentException If the prompt format is invalid. * @throws \RuntimeException If no suitable model is found. */ public static function generateImageResult($prompt, $modelOrConfig = null, ?ProviderRegistry $registry = null): GenerativeAiResult { self::validateModelOrConfigParameter($modelOrConfig); return self::getConfiguredPromptBuilder($prompt, $modelOrConfig, $registry)->generateImageResult(); } /** * Converts text to speech using the traditional API approach. * * @since 0.1.0 * * @param Prompt $prompt The prompt content. * @param ModelInterface|ModelConfig|null $modelOrConfig Optional specific model to use, * or model configuration for auto-discovery, * or null for defaults. * @param ProviderRegistry|null $registry Optional custom registry. If null, uses default. * @return GenerativeAiResult The generation result. * * @throws \InvalidArgumentException If the prompt format is invalid. * @throws \RuntimeException If no suitable model is found. */ public static function convertTextToSpeechResult($prompt, $modelOrConfig = null, ?ProviderRegistry $registry = null): GenerativeAiResult { self::validateModelOrConfigParameter($modelOrConfig); return self::getConfiguredPromptBuilder($prompt, $modelOrConfig, $registry)->convertTextToSpeechResult(); } /** * Generates speech using the traditional API approach. * * @since 0.1.0 * * @param Prompt $prompt The prompt content. * @param ModelInterface|ModelConfig|null $modelOrConfig Optional specific model to use, * or model configuration for auto-discovery, * or null for defaults. * @param ProviderRegistry|null $registry Optional custom registry. If null, uses default. * @return GenerativeAiResult The generation result. * * @throws \InvalidArgumentException If the prompt format is invalid. * @throws \RuntimeException If no suitable model is found. */ public static function generateSpeechResult($prompt, $modelOrConfig = null, ?ProviderRegistry $registry = null): GenerativeAiResult { self::validateModelOrConfigParameter($modelOrConfig); return self::getConfiguredPromptBuilder($prompt, $modelOrConfig, $registry)->generateSpeechResult(); } /** * Generates a video using the traditional API approach. * * @since 1.3.0 * * @param Prompt $prompt The prompt content. * @param ModelInterface|ModelConfig|null $modelOrConfig Optional specific model to use, * or model configuration for auto-discovery, * or null for defaults. * @param ProviderRegistry|null $registry Optional custom registry. If null, uses default. * @return GenerativeAiResult The generation result. * * @throws \InvalidArgumentException If the prompt format is invalid. * @throws \RuntimeException If no suitable model is found. */ public static function generateVideoResult($prompt, $modelOrConfig = null, ?ProviderRegistry $registry = null): GenerativeAiResult { self::validateModelOrConfigParameter($modelOrConfig); return self::getConfiguredPromptBuilder($prompt, $modelOrConfig, $registry)->generateVideoResult(); } /** * Creates a new message builder for fluent API usage. * * This method will be implemented once MessageBuilder is available. * MessageBuilder will provide a fluent interface for constructing complex * messages with multiple parts, attachments, and metadata. * * @since 0.1.0 * * @param string|null $text Optional initial message text. * @return object MessageBuilder instance (type will be updated when MessageBuilder is available). * * @throws \RuntimeException When MessageBuilder is not yet available. */ public static function message(?string $text = null) { throw new RuntimeException('MessageBuilder is not yet available. This method depends on builder infrastructure. ' . 'Use direct generation methods (generateTextResult, generateImageResult, etc.) for now.'); } /** * Validates that parameter is ModelInterface, ModelConfig, or null. * * @param mixed $modelOrConfig The parameter to validate. * @return void * @throws \InvalidArgumentException If parameter is invalid type. */ private static function validateModelOrConfigParameter($modelOrConfig): void { if ($modelOrConfig !== null && !$modelOrConfig instanceof ModelInterface && !$modelOrConfig instanceof ModelConfig) { throw new InvalidArgumentException('Parameter must be a ModelInterface instance (specific model), ' . 'ModelConfig instance (for auto-discovery), or null (default auto-discovery). ' . sprintf('Received: %s', is_object($modelOrConfig) ? get_class($modelOrConfig) : gettype($modelOrConfig))); } } /** * Configures PromptBuilder based on model/config parameter type. * * @param Prompt $prompt The prompt content. * @param ModelInterface|ModelConfig|null $modelOrConfig The model or config parameter. * @param ProviderRegistry|null $registry Optional custom registry to use. * @return PromptBuilder Configured prompt builder. */ private static function getConfiguredPromptBuilder($prompt, $modelOrConfig, ?ProviderRegistry $registry = null): PromptBuilder { $builder = self::prompt($prompt, $registry); if ($modelOrConfig instanceof ModelInterface) { $builder->usingModel($modelOrConfig); } elseif ($modelOrConfig instanceof ModelConfig) { $builder->usingModelConfig($modelOrConfig); } // null case: use default model discovery return $builder; } } ������������������������������������������������Events/AfterGenerateResultEvent.php�����������������������������������������������������������������0000644�����������������00000006353�15250554011�0013443 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Events; use WordPress\AiClient\Messages\DTO\Message; use WordPress\AiClient\Providers\Models\Contracts\ModelInterface; use WordPress\AiClient\Providers\Models\Enums\CapabilityEnum; use WordPress\AiClient\Results\DTO\GenerativeAiResult; /** * Event dispatched after a prompt has been sent to the AI model and a response received. * * This event allows listeners to inspect the result of the model call for logging, * analytics, or other post-processing purposes. The result object is immutable. * * @since 0.4.0 */ class AfterGenerateResultEvent { /** * @var list<Message> The messages that were sent to the model. */ private array $messages; /** * @var ModelInterface The model that processed the prompt. */ private ModelInterface $model; /** * @var CapabilityEnum|null The capability that was used for generation. */ private ?CapabilityEnum $capability; /** * @var GenerativeAiResult The result from the model. */ private GenerativeAiResult $result; /** * Constructor. * * @since 0.4.0 * * @param list<Message> $messages The messages that were sent to the model. * @param ModelInterface $model The model that processed the prompt. * @param CapabilityEnum|null $capability The capability that was used for generation. * @param GenerativeAiResult $result The result from the model. */ public function __construct(array $messages, ModelInterface $model, ?CapabilityEnum $capability, GenerativeAiResult $result) { $this->messages = $messages; $this->model = $model; $this->capability = $capability; $this->result = $result; } /** * Gets the messages that were sent to the model. * * @since 0.4.0 * * @return list<Message> The messages. */ public function getMessages(): array { return $this->messages; } /** * Gets the model that processed the prompt. * * @since 0.4.0 * * @return ModelInterface The model. */ public function getModel(): ModelInterface { return $this->model; } /** * Gets the capability that was used for generation. * * @since 0.4.0 * * @return CapabilityEnum|null The capability, or null if not specified. */ public function getCapability(): ?CapabilityEnum { return $this->capability; } /** * Gets the result from the model. * * @since 0.4.0 * * @return GenerativeAiResult The result. */ public function getResult(): GenerativeAiResult { return $this->result; } /** * Performs a deep clone of the event. * * This method ensures that message and result objects are cloned to prevent * modifications to the cloned event from affecting the original. * The model object is not cloned as it is a service object. * * @since 0.4.2 */ public function __clone() { $clonedMessages = []; foreach ($this->messages as $message) { $clonedMessages[] = clone $message; } $this->messages = $clonedMessages; $this->result = clone $this->result; } } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Events/BeforeGenerateResultEvent.php����������������������������������������������������������������0000644�����������������00000005251�15250554011�0013600 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Events; use WordPress\AiClient\Messages\DTO\Message; use WordPress\AiClient\Providers\Models\Contracts\ModelInterface; use WordPress\AiClient\Providers\Models\Enums\CapabilityEnum; /** * Event dispatched before a prompt is sent to the AI model. * * This event allows listeners to inspect and modify the messages before they * are sent to the model. The event is not stoppable, meaning the model call * will always proceed regardless of listener actions. * * @since 0.4.0 */ class BeforeGenerateResultEvent { /** * @var list<Message> The messages to be sent to the model. */ private array $messages; /** * @var ModelInterface The model that will process the prompt. */ private ModelInterface $model; /** * @var CapabilityEnum|null The capability being used for generation. */ private ?CapabilityEnum $capability; /** * Constructor. * * @since 0.4.0 * * @param list<Message> $messages The messages to be sent to the model. * @param ModelInterface $model The model that will process the prompt. * @param CapabilityEnum|null $capability The capability being used for generation. */ public function __construct(array $messages, ModelInterface $model, ?CapabilityEnum $capability) { $this->messages = $messages; $this->model = $model; $this->capability = $capability; } /** * Gets the messages to be sent to the model. * * @since 0.4.0 * * @return list<Message> The messages. */ public function getMessages(): array { return $this->messages; } /** * Gets the model that will process the prompt. * * @since 0.4.0 * * @return ModelInterface The model. */ public function getModel(): ModelInterface { return $this->model; } /** * Gets the capability being used for generation. * * @since 0.4.0 * * @return CapabilityEnum|null The capability, or null if not specified. */ public function getCapability(): ?CapabilityEnum { return $this->capability; } /** * Performs a deep clone of the event. * * This method ensures that message objects are cloned to prevent * modifications to the cloned event from affecting the original. * The model object is not cloned as it is a service object. * * @since 0.4.2 */ public function __clone() { $clonedMessages = []; foreach ($this->messages as $message) { $clonedMessages[] = clone $message; } $this->messages = $clonedMessages; } } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Messages/DTO/MessagePart.php������������������������������������������������������������������������0000644�����������������00000025055�15250554011�0011672 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Messages\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Common\Exception\RuntimeException; use WordPress\AiClient\Files\DTO\File; use WordPress\AiClient\Messages\Enums\MessagePartChannelEnum; use WordPress\AiClient\Messages\Enums\MessagePartTypeEnum; use WordPress\AiClient\Tools\DTO\FunctionCall; use WordPress\AiClient\Tools\DTO\FunctionResponse; /** * Represents a part of a message. * * Messages can contain multiple parts of different types, such as text, files, * function calls, etc. This DTO encapsulates one such part. * * @since 0.1.0 * * @phpstan-import-type FileArrayShape from File * @phpstan-import-type FunctionCallArrayShape from FunctionCall * @phpstan-import-type FunctionResponseArrayShape from FunctionResponse * * @phpstan-type MessagePartArrayShape array{ * channel: string, * type: string, * thoughtSignature?: string, * text?: string, * file?: FileArrayShape, * functionCall?: FunctionCallArrayShape, * functionResponse?: FunctionResponseArrayShape * } * * @extends AbstractDataTransferObject<MessagePartArrayShape> */ class MessagePart extends AbstractDataTransferObject { public const KEY_CHANNEL = 'channel'; public const KEY_TYPE = 'type'; public const KEY_THOUGHT_SIGNATURE = 'thoughtSignature'; public const KEY_TEXT = 'text'; public const KEY_FILE = 'file'; public const KEY_FUNCTION_CALL = 'functionCall'; public const KEY_FUNCTION_RESPONSE = 'functionResponse'; /** * @var MessagePartChannelEnum The channel this message part belongs to. */ private MessagePartChannelEnum $channel; /** * @var MessagePartTypeEnum The type of this message part. */ private MessagePartTypeEnum $type; /** * @var string|null Thought signature for extended thinking. */ private ?string $thoughtSignature = null; /** * @var string|null Text content (when type is TEXT). */ private ?string $text = null; /** * @var File|null File data (when type is FILE). */ private ?File $file = null; /** * @var FunctionCall|null Function call request (when type is FUNCTION_CALL). */ private ?FunctionCall $functionCall = null; /** * @var FunctionResponse|null Function response (when type is FUNCTION_RESPONSE). */ private ?FunctionResponse $functionResponse = null; /** * Constructor that accepts various content types and infers the message part type. * * @since 0.1.0 * * @param mixed $content The content of this message part. * @param MessagePartChannelEnum|null $channel The channel this part belongs to. Defaults to CONTENT. * @param string|null $thoughtSignature Optional thought signature for extended thinking. * @throws InvalidArgumentException If an unsupported content type is provided. */ public function __construct($content, ?MessagePartChannelEnum $channel = null, ?string $thoughtSignature = null) { $this->channel = $channel ?? MessagePartChannelEnum::content(); $this->thoughtSignature = $thoughtSignature; if (is_string($content)) { $this->type = MessagePartTypeEnum::text(); $this->text = $content; } elseif ($content instanceof File) { $this->type = MessagePartTypeEnum::file(); $this->file = $content; } elseif ($content instanceof FunctionCall) { $this->type = MessagePartTypeEnum::functionCall(); $this->functionCall = $content; } elseif ($content instanceof FunctionResponse) { $this->type = MessagePartTypeEnum::functionResponse(); $this->functionResponse = $content; } else { $type = is_object($content) ? get_class($content) : gettype($content); throw new InvalidArgumentException(sprintf('Unsupported content type %s. Expected string, File, ' . 'FunctionCall, or FunctionResponse.', $type)); } } /** * Gets the channel this message part belongs to. * * @since 0.1.0 * * @return MessagePartChannelEnum The channel. */ public function getChannel(): MessagePartChannelEnum { return $this->channel; } /** * Gets the type of this message part. * * @since 0.1.0 * * @return MessagePartTypeEnum The type. */ public function getType(): MessagePartTypeEnum { return $this->type; } /** * Gets the thought signature. * * @since 1.3.0 * * @return string|null The thought signature or null if not set. */ public function getThoughtSignature(): ?string { return $this->thoughtSignature; } /** * Gets the text content. * * @since 0.1.0 * * @return string|null The text content or null if not a text part. */ public function getText(): ?string { return $this->text; } /** * Gets the file. * * @since 0.1.0 * * @return File|null The file or null if not a file part. */ public function getFile(): ?File { return $this->file; } /** * Gets the function call. * * @since 0.1.0 * * @return FunctionCall|null The function call or null if not a function call part. */ public function getFunctionCall(): ?FunctionCall { return $this->functionCall; } /** * Gets the function response. * * @since 0.1.0 * * @return FunctionResponse|null The function response or null if not a function response part. */ public function getFunctionResponse(): ?FunctionResponse { return $this->functionResponse; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { $channelSchema = ['type' => 'string', 'enum' => MessagePartChannelEnum::getValues(), 'description' => 'The channel this message part belongs to.']; $thoughtSignatureSchema = ['type' => 'string', 'description' => 'Thought signature for extended thinking.']; return ['oneOf' => [['type' => 'object', 'properties' => [self::KEY_CHANNEL => $channelSchema, self::KEY_TYPE => ['type' => 'string', 'const' => MessagePartTypeEnum::text()->value], self::KEY_TEXT => ['type' => 'string', 'description' => 'Text content.'], self::KEY_THOUGHT_SIGNATURE => $thoughtSignatureSchema], 'required' => [self::KEY_TYPE, self::KEY_TEXT], 'additionalProperties' => \false], ['type' => 'object', 'properties' => [self::KEY_CHANNEL => $channelSchema, self::KEY_TYPE => ['type' => 'string', 'const' => MessagePartTypeEnum::file()->value], self::KEY_FILE => File::getJsonSchema(), self::KEY_THOUGHT_SIGNATURE => $thoughtSignatureSchema], 'required' => [self::KEY_TYPE, self::KEY_FILE], 'additionalProperties' => \false], ['type' => 'object', 'properties' => [self::KEY_CHANNEL => $channelSchema, self::KEY_TYPE => ['type' => 'string', 'const' => MessagePartTypeEnum::functionCall()->value], self::KEY_FUNCTION_CALL => FunctionCall::getJsonSchema(), self::KEY_THOUGHT_SIGNATURE => $thoughtSignatureSchema], 'required' => [self::KEY_TYPE, self::KEY_FUNCTION_CALL], 'additionalProperties' => \false], ['type' => 'object', 'properties' => [self::KEY_CHANNEL => $channelSchema, self::KEY_TYPE => ['type' => 'string', 'const' => MessagePartTypeEnum::functionResponse()->value], self::KEY_FUNCTION_RESPONSE => FunctionResponse::getJsonSchema(), self::KEY_THOUGHT_SIGNATURE => $thoughtSignatureSchema], 'required' => [self::KEY_TYPE, self::KEY_FUNCTION_RESPONSE], 'additionalProperties' => \false]]]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return MessagePartArrayShape */ public function toArray(): array { $data = [self::KEY_CHANNEL => $this->channel->value, self::KEY_TYPE => $this->type->value]; if ($this->text !== null) { $data[self::KEY_TEXT] = $this->text; } elseif ($this->file !== null) { $data[self::KEY_FILE] = $this->file->toArray(); } elseif ($this->functionCall !== null) { $data[self::KEY_FUNCTION_CALL] = $this->functionCall->toArray(); } elseif ($this->functionResponse !== null) { $data[self::KEY_FUNCTION_RESPONSE] = $this->functionResponse->toArray(); } else { throw new RuntimeException('MessagePart requires one of: text, file, functionCall, or functionResponse. ' . 'This should not be a possible condition.'); } if ($this->thoughtSignature !== null) { $data[self::KEY_THOUGHT_SIGNATURE] = $this->thoughtSignature; } return $data; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function fromArray(array $array): self { if (isset($array[self::KEY_CHANNEL])) { $channel = MessagePartChannelEnum::from($array[self::KEY_CHANNEL]); } else { $channel = null; } $thoughtSignature = $array[self::KEY_THOUGHT_SIGNATURE] ?? null; // Check which properties are set to determine how to construct the MessagePart if (isset($array[self::KEY_TEXT])) { return new self($array[self::KEY_TEXT], $channel, $thoughtSignature); } elseif (isset($array[self::KEY_FILE])) { return new self(File::fromArray($array[self::KEY_FILE]), $channel, $thoughtSignature); } elseif (isset($array[self::KEY_FUNCTION_CALL])) { return new self(FunctionCall::fromArray($array[self::KEY_FUNCTION_CALL]), $channel, $thoughtSignature); } elseif (isset($array[self::KEY_FUNCTION_RESPONSE])) { return new self(FunctionResponse::fromArray($array[self::KEY_FUNCTION_RESPONSE]), $channel, $thoughtSignature); } else { throw new InvalidArgumentException('MessagePart requires one of: text, file, functionCall, or functionResponse.'); } } /** * Performs a deep clone of the message part. * * This method ensures that nested objects (file, function call, function response) * are cloned to prevent modifications to the cloned part from affecting the original. * * @since 0.4.2 */ public function __clone() { if ($this->file !== null) { $this->file = clone $this->file; } if ($this->functionCall !== null) { $this->functionCall = clone $this->functionCall; } if ($this->functionResponse !== null) { $this->functionResponse = clone $this->functionResponse; } } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Messages/DTO/error_log������������������������������������������������������������������������������0000644�����������������00000043435�15250554011�0010665 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������[15-Jul-2026 16:16:34 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php on line 18 [15-Jul-2026 16:16:34 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php on line 19 [15-Jul-2026 16:16:34 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php:38 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php on line 38 [15-Jul-2026 16:16:34 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/Message.php:26 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/Message.php on line 26 [24-Jul-2026 12:15:36 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php on line 19 [24-Jul-2026 12:15:36 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php:38 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php on line 38 [24-Jul-2026 12:15:36 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/Message.php:26 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/Message.php on line 26 [24-Jul-2026 12:15:37 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php on line 18 [25-Jul-2026 05:33:40 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php on line 19 [25-Jul-2026 05:33:40 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/Message.php:26 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/Message.php on line 26 [25-Jul-2026 05:33:40 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php:38 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php on line 38 [25-Jul-2026 05:33:41 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php on line 18 [04-Aug-2026 23:27:16 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php on line 19 [04-Aug-2026 23:27:16 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php:38 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php on line 38 [04-Aug-2026 23:27:16 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/Message.php:26 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/Message.php on line 26 [04-Aug-2026 23:27:17 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php on line 18 [13-Aug-2026 01:32:55 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php:38 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php on line 38 [13-Aug-2026 01:32:55 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php on line 18 [13-Aug-2026 01:32:55 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/Message.php:26 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/Message.php on line 26 [13-Aug-2026 01:32:56 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php on line 19 [13-Aug-2026 13:00:23 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/Message.php:26 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/Message.php on line 26 [13-Aug-2026 13:00:24 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php on line 18 [13-Aug-2026 13:00:24 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php:38 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php on line 38 [13-Aug-2026 13:00:24 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php on line 19 [19-Aug-2026 21:32:05 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php:38 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php on line 38 [19-Aug-2026 21:32:05 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php on line 19 [19-Aug-2026 21:32:05 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php on line 18 [19-Aug-2026 21:32:05 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/Message.php:26 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/Message.php on line 26 [19-Aug-2026 22:05:34 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php on line 18 [19-Aug-2026 22:05:34 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php on line 19 [19-Aug-2026 22:05:34 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/Message.php:26 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/Message.php on line 26 [19-Aug-2026 22:05:35 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php:38 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php on line 38 [19-Aug-2026 23:23:11 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php:38 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php on line 38 [19-Aug-2026 23:23:11 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php on line 19 [19-Aug-2026 23:23:11 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/Message.php:26 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/Message.php on line 26 [19-Aug-2026 23:23:12 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php on line 18 [03-Sep-2026 06:56:33 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php on line 19 [03-Sep-2026 06:56:34 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php:38 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php on line 38 [05-Sep-2026 10:46:21 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php on line 19 [05-Sep-2026 10:46:57 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/Message.php:26 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/Message.php on line 26 [05-Sep-2026 17:19:22 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php:38 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php on line 38 [05-Sep-2026 17:19:25 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php on line 18 [06-Sep-2026 12:02:19 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php on line 19 [06-Sep-2026 12:02:19 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/Message.php:26 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/Message.php on line 26 [06-Sep-2026 23:35:08 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php:38 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php on line 38 [06-Sep-2026 23:35:08 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php on line 18 [08-Sep-2026 20:17:15 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php:38 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php on line 38 [08-Sep-2026 20:17:15 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php on line 18 [08-Sep-2026 20:17:15 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/Message.php:26 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/Message.php on line 26 [09-Sep-2026 03:23:27 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php on line 19 �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Messages/DTO/Message.php����������������������������������������������������������������������������0000644�����������������00000013044�15250554011�0011036 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Messages\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Messages\Enums\MessageRoleEnum; /** * Represents a message in an AI conversation. * * Messages are the fundamental unit of communication with AI models, * containing a role and one or more parts with different content types. * * @since 0.1.0 * * @phpstan-import-type MessagePartArrayShape from MessagePart * * @phpstan-type MessageArrayShape array{ * role: string, * parts: array<MessagePartArrayShape> * } * * @extends AbstractDataTransferObject<MessageArrayShape> */ class Message extends AbstractDataTransferObject { public const KEY_ROLE = 'role'; public const KEY_PARTS = 'parts'; /** * @var MessageRoleEnum The role of the message sender. */ protected MessageRoleEnum $role; /** * @var MessagePart[] The parts that make up this message. */ protected array $parts; /** * Constructor. * * @since 0.1.0 * * @param MessageRoleEnum $role The role of the message sender. * @param MessagePart[] $parts The parts that make up this message. * @throws InvalidArgumentException If parts contain invalid content for the role. */ public function __construct(MessageRoleEnum $role, array $parts) { $this->role = $role; $this->parts = $parts; $this->validateParts(); } /** * Gets the role of the message sender. * * @since 0.1.0 * * @return MessageRoleEnum The role. */ public function getRole(): MessageRoleEnum { return $this->role; } /** * Gets the message parts. * * @since 0.1.0 * * @return MessagePart[] The message parts. */ public function getParts(): array { return $this->parts; } /** * Returns a new instance with the given part appended. * * @since 0.1.0 * * @param MessagePart $part The part to append. * @return Message A new instance with the part appended. * @throws InvalidArgumentException If the part is invalid for the role. */ public function withPart(\WordPress\AiClient\Messages\DTO\MessagePart $part): \WordPress\AiClient\Messages\DTO\Message { $newParts = $this->parts; $newParts[] = $part; return new \WordPress\AiClient\Messages\DTO\Message($this->role, $newParts); } /** * Validates that the message parts are appropriate for the message role. * * @since 0.1.0 * * @return void * @throws InvalidArgumentException If validation fails. */ private function validateParts(): void { foreach ($this->parts as $part) { $type = $part->getType(); if ($this->role->isUser() && $type->isFunctionCall()) { throw new InvalidArgumentException('User messages cannot contain function calls.'); } if ($this->role->isModel() && $type->isFunctionResponse()) { throw new InvalidArgumentException('Model messages cannot contain function responses.'); } } } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_ROLE => ['type' => 'string', 'enum' => MessageRoleEnum::getValues(), 'description' => 'The role of the message sender.'], self::KEY_PARTS => ['type' => 'array', 'items' => \WordPress\AiClient\Messages\DTO\MessagePart::getJsonSchema(), 'minItems' => 1, 'description' => 'The parts that make up this message.']], 'required' => [self::KEY_ROLE, self::KEY_PARTS]]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return MessageArrayShape */ public function toArray(): array { return [self::KEY_ROLE => $this->role->value, self::KEY_PARTS => array_map(function (\WordPress\AiClient\Messages\DTO\MessagePart $part) { return $part->toArray(); }, $this->parts)]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return self The specific message class based on the role. */ final public static function fromArray(array $array): self { static::validateFromArrayData($array, [self::KEY_ROLE, self::KEY_PARTS]); $role = MessageRoleEnum::from($array[self::KEY_ROLE]); $partsData = $array[self::KEY_PARTS]; $parts = array_map(function (array $partData) { return \WordPress\AiClient\Messages\DTO\MessagePart::fromArray($partData); }, $partsData); // Determine which concrete class to instantiate based on role if ($role->isUser()) { return new \WordPress\AiClient\Messages\DTO\UserMessage($parts); } elseif ($role->isModel()) { return new \WordPress\AiClient\Messages\DTO\ModelMessage($parts); } else { // Only USER and MODEL roles are supported throw new InvalidArgumentException('Invalid message role: ' . $role->value); } } /** * Performs a deep clone of the message. * * This method ensures that message part objects are cloned to prevent * modifications to the cloned message from affecting the original. * * @since 0.4.2 */ public function __clone() { $clonedParts = []; foreach ($this->parts as $part) { $clonedParts[] = clone $part; } $this->parts = $clonedParts; } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Messages/DTO/UserMessage.php������������������������������������������������������������������������0000644�����������������00000001454�15250554011�0011677 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Messages\DTO; use WordPress\AiClient\Messages\Enums\MessageRoleEnum; /** * Represents a message from a user. * * This is a convenience class that automatically sets the role to USER. * * Important: Do not rely on `instanceof UserMessage` to determine the message role. * This is merely a helper class for construction. Always use `$message->getRole()` * to check the role of a message. * * @since 0.1.0 */ class UserMessage extends \WordPress\AiClient\Messages\DTO\Message { /** * Constructor. * * @since 0.1.0 * * @param MessagePart[] $parts The parts that make up this message. */ public function __construct(array $parts) { parent::__construct(MessageRoleEnum::user(), $parts); } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Messages/DTO/ModelMessage.php�����������������������������������������������������������������������0000644�����������������00000001544�15250554011�0012021 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Messages\DTO; use WordPress\AiClient\Messages\Enums\MessageRoleEnum; /** * Represents a message from the AI model. * * This is a convenience class that automatically sets the role to MODEL. * Model messages contain the AI's responses. * * Important: Do not rely on `instanceof ModelMessage` to determine the message role. * This is merely a helper class for construction. Always use `$message->getRole()` * to check the role of a message. * * @since 0.1.0 */ class ModelMessage extends \WordPress\AiClient\Messages\DTO\Message { /** * Constructor. * * @since 0.1.0 * * @param MessagePart[] $parts The parts that make up this message. */ public function __construct(array $parts) { parent::__construct(MessageRoleEnum::model(), $parts); } } ������������������������������������������������������������������������������������������������������������������������������������������������������������Messages/Enums/MessageRoleEnum.php������������������������������������������������������������������0000644�����������������00000001244�15250554011�0013145 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Messages\Enums; use WordPress\AiClient\Common\AbstractEnum; /** * Enum for message roles in AI conversations. * * @since 0.1.0 * * @method static self user() Creates an instance for USER role. * @method static self model() Creates an instance for MODEL role. * @method bool isUser() Checks if the role is USER. * @method bool isModel() Checks if the role is MODEL. */ class MessageRoleEnum extends AbstractEnum { /** * User role - messages from the user. */ public const USER = 'user'; /** * Model role - messages from the AI model. */ public const MODEL = 'model'; } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Messages/Enums/ModalityEnum.php���������������������������������������������������������������������0000644�����������������00000002407�15250554011�0012523 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Messages\Enums; use WordPress\AiClient\Common\AbstractEnum; /** * Enum for input/output modalities. * * @since 0.1.0 * * @method static self text() Creates an instance for TEXT modality. * @method static self document() Creates an instance for DOCUMENT modality. * @method static self image() Creates an instance for IMAGE modality. * @method static self audio() Creates an instance for AUDIO modality. * @method static self video() Creates an instance for VIDEO modality. * @method bool isText() Checks if the modality is TEXT. * @method bool isDocument() Checks if the modality is DOCUMENT. * @method bool isImage() Checks if the modality is IMAGE. * @method bool isAudio() Checks if the modality is AUDIO. * @method bool isVideo() Checks if the modality is VIDEO. */ class ModalityEnum extends AbstractEnum { /** * Text modality. */ public const TEXT = 'text'; /** * Document modality (PDFs, Word docs, etc.). */ public const DOCUMENT = 'document'; /** * Image modality. */ public const IMAGE = 'image'; /** * Audio modality. */ public const AUDIO = 'audio'; /** * Video modality. */ public const VIDEO = 'video'; } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Messages/Enums/error_log����������������������������������������������������������������������������0000644�����������������00000045770�15250554011�0011332 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������[15-Jul-2026 16:16:35 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php on line 23 [15-Jul-2026 16:16:35 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php on line 21 [15-Jul-2026 16:16:35 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php on line 17 [15-Jul-2026 16:16:35 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php on line 17 [24-Jul-2026 12:15:35 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php on line 23 [24-Jul-2026 12:15:35 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php on line 17 [24-Jul-2026 12:15:35 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php on line 21 [24-Jul-2026 12:15:36 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php on line 17 [25-Jul-2026 05:33:38 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php on line 17 [25-Jul-2026 05:33:38 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php on line 21 [25-Jul-2026 05:33:39 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php on line 23 [25-Jul-2026 05:33:39 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php on line 17 [04-Aug-2026 23:27:14 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php on line 17 [04-Aug-2026 23:27:14 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php on line 17 [04-Aug-2026 23:27:14 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php on line 21 [04-Aug-2026 23:27:15 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php on line 23 [13-Aug-2026 01:32:57 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php on line 23 [13-Aug-2026 01:32:57 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php on line 17 [13-Aug-2026 01:32:58 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php on line 21 [13-Aug-2026 01:32:58 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php on line 17 [13-Aug-2026 13:00:21 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php on line 17 [13-Aug-2026 13:00:21 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php on line 21 [13-Aug-2026 13:00:21 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php on line 23 [13-Aug-2026 13:00:22 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php on line 17 [19-Aug-2026 21:32:05 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php on line 23 [19-Aug-2026 21:32:05 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php on line 17 [19-Aug-2026 21:32:05 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php on line 21 [19-Aug-2026 21:32:05 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php on line 17 [19-Aug-2026 22:05:32 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php on line 23 [19-Aug-2026 22:05:32 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php on line 17 [19-Aug-2026 22:05:32 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php on line 21 [19-Aug-2026 22:05:32 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php on line 17 [19-Aug-2026 23:23:14 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php on line 17 [19-Aug-2026 23:23:14 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php on line 21 [19-Aug-2026 23:23:14 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php on line 17 [19-Aug-2026 23:23:15 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php on line 23 [03-Sep-2026 06:47:52 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php on line 23 [03-Sep-2026 06:51:06 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php on line 21 [03-Sep-2026 06:51:21 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php on line 17 [03-Sep-2026 06:51:52 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php on line 17 [05-Sep-2026 13:07:15 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php on line 17 [05-Sep-2026 13:07:25 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php on line 23 [05-Sep-2026 20:48:37 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php on line 21 [05-Sep-2026 22:28:54 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php on line 17 [06-Sep-2026 14:52:35 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php on line 23 [06-Sep-2026 14:52:35 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php on line 17 [06-Sep-2026 23:35:08 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php on line 21 [06-Sep-2026 23:54:11 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php on line 17 [10-Sep-2026 02:36:49 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php on line 17 [10-Sep-2026 03:54:04 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php:21 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php on line 21 [10-Sep-2026 03:54:05 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php on line 17 [10-Sep-2026 03:54:05 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php on line 23 ��������Messages/Enums/MessagePartChannelEnum.php�����������������������������������������������������������0000644�����������������00000001264�15250554011�0014445 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Messages\Enums; use WordPress\AiClient\Common\AbstractEnum; /** * Enum for message part channels. * * @since 0.1.0 * * @method static self content() Creates an instance for CONTENT channel. * @method static self thought() Creates an instance for THOUGHT channel. * @method bool isContent() Checks if the channel is CONTENT. * @method bool isThought() Checks if the channel is THOUGHT. */ class MessagePartChannelEnum extends AbstractEnum { /** * Regular (primary) content. */ public const CONTENT = 'content'; /** * Model thinking or reasoning. */ public const THOUGHT = 'thought'; } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Messages/Enums/MessagePartTypeEnum.php��������������������������������������������������������������0000644�����������������00000002171�15250554011�0014014 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Messages\Enums; use WordPress\AiClient\Common\AbstractEnum; /** * Enum for message part types. * * @since 0.1.0 * * @method static self text() Creates an instance for TEXT type. * @method static self file() Creates an instance for FILE type. * @method static self functionCall() Creates an instance for FUNCTION_CALL type. * @method static self functionResponse() Creates an instance for FUNCTION_RESPONSE type. * @method bool isText() Checks if the type is TEXT. * @method bool isFile() Checks if the type is FILE. * @method bool isFunctionCall() Checks if the type is FUNCTION_CALL. * @method bool isFunctionResponse() Checks if the type is FUNCTION_RESPONSE. */ class MessagePartTypeEnum extends AbstractEnum { /** * Text content. */ public const TEXT = 'text'; /** * File content (inline or remote). */ public const FILE = 'file'; /** * Function call request. */ public const FUNCTION_CALL = 'function_call'; /** * Function response. */ public const FUNCTION_RESPONSE = 'function_response'; } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Operations/DTO/error_log����������������������������������������������������������������������������0000644�����������������00000012034�15250554011�0011230 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������[15-Jul-2026 16:16:36 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php on line 24 [24-Jul-2026 12:15:10 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php on line 24 [25-Jul-2026 05:33:44 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php on line 24 [04-Aug-2026 23:28:03 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php on line 24 [13-Aug-2026 01:33:00 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php on line 24 [13-Aug-2026 12:59:41 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php on line 24 [19-Aug-2026 21:32:07 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php on line 24 [19-Aug-2026 22:06:34 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php on line 24 [19-Aug-2026 23:23:28 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php on line 24 [03-Sep-2026 06:32:38 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php on line 24 [03-Sep-2026 16:14:32 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php on line 24 [04-Sep-2026 22:07:20 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php on line 24 [08-Sep-2026 20:29:58 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php on line 24 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Operations/DTO/GenerativeAiOperation.php������������������������������������������������������������0000644�����������������00000012132�15250554011�0014247 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Operations\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; use WordPress\AiClient\Operations\Contracts\OperationInterface; use WordPress\AiClient\Operations\Enums\OperationStateEnum; use WordPress\AiClient\Results\DTO\GenerativeAiResult; /** * Represents a long-running generative AI operation. * * This DTO tracks the progress of generative AI tasks that may not complete * immediately, providing access to the result once available. * * @since 0.1.0 * * @phpstan-import-type GenerativeAiResultArrayShape from GenerativeAiResult * * @phpstan-type GenerativeAiOperationArrayShape array{id: string, state: string, result?: GenerativeAiResultArrayShape} * * @extends AbstractDataTransferObject<GenerativeAiOperationArrayShape> */ class GenerativeAiOperation extends AbstractDataTransferObject implements OperationInterface { public const KEY_ID = 'id'; public const KEY_STATE = 'state'; public const KEY_RESULT = 'result'; /** * @var string Unique identifier for this operation. */ private string $id; /** * @var OperationStateEnum The current state of the operation. */ private OperationStateEnum $state; /** * @var GenerativeAiResult|null The result once the operation completes. */ private ?GenerativeAiResult $result; /** * Constructor. * * @since 0.1.0 * * @param string $id Unique identifier for this operation. * @param OperationStateEnum $state The current state of the operation. * @param GenerativeAiResult|null $result The result once the operation completes. */ public function __construct(string $id, OperationStateEnum $state, ?GenerativeAiResult $result = null) { $this->id = $id; $this->state = $state; $this->result = $result; } /** * Creates a deep clone of this operation. * * Clones the result object if present to ensure the cloned * operation is independent of the original. * The state enum is immutable and can be safely shared. * * @since 0.4.2 */ public function __clone() { // Clone the result if present (GenerativeAiResult has __clone) if ($this->result !== null) { $this->result = clone $this->result; } // Note: $state is an immutable enum and can be safely shared } /** * {@inheritDoc} * * @since 0.1.0 */ public function getId(): string { return $this->id; } /** * {@inheritDoc} * * @since 0.1.0 */ public function getState(): OperationStateEnum { return $this->state; } /** * Gets the operation result. * * @since 0.1.0 * * @return GenerativeAiResult|null The result or null if not yet complete. */ public function getResult(): ?GenerativeAiResult { return $this->result; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['oneOf' => [ // Succeeded state - has result ['type' => 'object', 'properties' => [self::KEY_ID => ['type' => 'string', 'description' => 'Unique identifier for this operation.'], self::KEY_STATE => ['type' => 'string', 'const' => OperationStateEnum::succeeded()->value], self::KEY_RESULT => GenerativeAiResult::getJsonSchema()], 'required' => [self::KEY_ID, self::KEY_STATE, self::KEY_RESULT], 'additionalProperties' => \false], // All other states - no result ['type' => 'object', 'properties' => [self::KEY_ID => ['type' => 'string', 'description' => 'Unique identifier for this operation.'], self::KEY_STATE => ['type' => 'string', 'enum' => [OperationStateEnum::starting()->value, OperationStateEnum::processing()->value, OperationStateEnum::failed()->value, OperationStateEnum::canceled()->value], 'description' => 'The current state of the operation.']], 'required' => [self::KEY_ID, self::KEY_STATE], 'additionalProperties' => \false], ]]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return GenerativeAiOperationArrayShape */ public function toArray(): array { $data = [self::KEY_ID => $this->id, self::KEY_STATE => $this->state->value]; if ($this->result !== null) { $data[self::KEY_RESULT] = $this->result->toArray(); } return $data; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function fromArray(array $array): self { static::validateFromArrayData($array, [self::KEY_ID, self::KEY_STATE]); $state = OperationStateEnum::from($array[self::KEY_STATE]); if ($state->isSucceeded()) { // If the operation has succeeded, it must have a result static::validateFromArrayData($array, [self::KEY_RESULT]); } $result = null; if (isset($array[self::KEY_RESULT])) { $result = GenerativeAiResult::fromArray($array[self::KEY_RESULT]); } return new self($array[self::KEY_ID], $state, $result); } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Operations/Contracts/OperationInterface.php���������������������������������������������������������0000644�����������������00000001413�15250554011�0015116 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Operations\Contracts; use WordPress\AiClient\Operations\Enums\OperationStateEnum; /** * Interface for AI operations. * * Operations represent long-running AI tasks that may not complete immediately. * They provide a way to track the progress and retrieve results asynchronously. * * @since 0.1.0 */ interface OperationInterface { /** * Gets the operation ID. * * @since 0.1.0 * * @return string The unique operation identifier. */ public function getId(): string; /** * Gets the current state of the operation. * * @since 0.1.0 * * @return OperationStateEnum The operation state. */ public function getState(): OperationStateEnum; } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Operations/Enums/OperationStateEnum.php�������������������������������������������������������������0000644�����������������00000002503�15250554011�0014253 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Operations\Enums; use WordPress\AiClient\Common\AbstractEnum; /** * Enum for operation states. * * @since 0.1.0 * * @method static self starting() Creates an instance for STARTING state. * @method static self processing() Creates an instance for PROCESSING state. * @method static self succeeded() Creates an instance for SUCCEEDED state. * @method static self failed() Creates an instance for FAILED state. * @method static self canceled() Creates an instance for CANCELED state. * @method bool isStarting() Checks if the state is STARTING. * @method bool isProcessing() Checks if the state is PROCESSING. * @method bool isSucceeded() Checks if the state is SUCCEEDED. * @method bool isFailed() Checks if the state is FAILED. * @method bool isCanceled() Checks if the state is CANCELED. */ class OperationStateEnum extends AbstractEnum { /** * Operation is starting. */ public const STARTING = 'starting'; /** * Operation is processing. */ public const PROCESSING = 'processing'; /** * Operation succeeded. */ public const SUCCEEDED = 'succeeded'; /** * Operation failed. */ public const FAILED = 'failed'; /** * Operation was canceled. */ public const CANCELED = 'canceled'; } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Operations/Enums/error_log��������������������������������������������������������������������������0000644�����������������00000011514�15250554011�0011673 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������[15-Jul-2026 16:16:36 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php on line 23 [24-Jul-2026 12:15:10 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php on line 23 [25-Jul-2026 05:33:45 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php on line 23 [04-Aug-2026 23:28:02 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php on line 23 [13-Aug-2026 01:33:01 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php on line 23 [13-Aug-2026 12:59:39 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php on line 23 [19-Aug-2026 21:32:07 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php on line 23 [19-Aug-2026 22:06:36 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php on line 23 [19-Aug-2026 23:23:31 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php on line 23 [03-Sep-2026 06:52:39 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php on line 23 [04-Sep-2026 06:37:35 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php on line 23 [05-Sep-2026 08:00:48 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php on line 23 [08-Sep-2026 22:13:13 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php on line 23 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Builders/PromptBuilder.php��������������������������������������������������������������������������0000644�����������������00000153503�15250554011�0011623 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Builders; use WordPress\AiClientDependencies\Psr\EventDispatcher\EventDispatcherInterface; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Common\Exception\RuntimeException; use WordPress\AiClient\Events\AfterGenerateResultEvent; use WordPress\AiClient\Events\BeforeGenerateResultEvent; use WordPress\AiClient\Files\DTO\File; use WordPress\AiClient\Files\Enums\FileTypeEnum; use WordPress\AiClient\Files\Enums\MediaOrientationEnum; use WordPress\AiClient\Messages\DTO\Message; use WordPress\AiClient\Messages\DTO\MessagePart; use WordPress\AiClient\Messages\DTO\UserMessage; use WordPress\AiClient\Messages\Enums\MessageRoleEnum; use WordPress\AiClient\Messages\Enums\ModalityEnum; use WordPress\AiClient\Providers\ApiBasedImplementation\Contracts\ApiBasedModelInterface; use WordPress\AiClient\Providers\Http\DTO\RequestOptions; use WordPress\AiClient\Providers\Models\Contracts\ModelInterface; use WordPress\AiClient\Providers\Models\DTO\ModelConfig; use WordPress\AiClient\Providers\Models\DTO\ModelMetadata; use WordPress\AiClient\Providers\Models\DTO\ModelRequirements; use WordPress\AiClient\Providers\Models\Enums\CapabilityEnum; use WordPress\AiClient\Providers\Models\ImageGeneration\Contracts\ImageGenerationModelInterface; use WordPress\AiClient\Providers\Models\SpeechGeneration\Contracts\SpeechGenerationModelInterface; use WordPress\AiClient\Providers\Models\TextGeneration\Contracts\TextGenerationModelInterface; use WordPress\AiClient\Providers\Models\TextToSpeechConversion\Contracts\TextToSpeechConversionModelInterface; use WordPress\AiClient\Providers\Models\VideoGeneration\Contracts\VideoGenerationModelInterface; use WordPress\AiClient\Providers\ProviderRegistry; use WordPress\AiClient\Results\DTO\GenerativeAiResult; use WordPress\AiClient\Tools\DTO\FunctionDeclaration; use WordPress\AiClient\Tools\DTO\FunctionResponse; use WordPress\AiClient\Tools\DTO\WebSearch; /** * Fluent builder for constructing AI prompts. * * This class provides a fluent interface for building prompts with various * content types and model configurations. It automatically infers model * requirements based on the features used in the prompt. * * @since 0.1.0 * * @phpstan-import-type MessageArrayShape from Message * @phpstan-import-type MessagePartArrayShape from MessagePart * * @phpstan-type Prompt string|MessagePart|Message|MessageArrayShape|list<string|MessagePart|MessagePartArrayShape>|list<Message>|null */ class PromptBuilder { /** * @var ProviderRegistry The provider registry for finding suitable models. */ private ProviderRegistry $registry; /** * @var list<Message> The messages in the conversation. */ protected array $messages = []; /** * @var ModelInterface|null The model to use for generation. */ protected ?ModelInterface $model = null; /** * @var list<string> Ordered list of preference keys to check when selecting a model. */ protected array $modelPreferenceKeys = []; /** * @var string|null The provider ID or class name. */ protected ?string $providerIdOrClassName = null; /** * @var ModelConfig The model configuration. */ protected ModelConfig $modelConfig; /** * @var RequestOptions|null The request options for HTTP transport. */ protected ?RequestOptions $requestOptions = null; /** * @var EventDispatcherInterface|null The event dispatcher for prompt lifecycle events. */ private ?EventDispatcherInterface $eventDispatcher = null; // phpcs:disable Generic.Files.LineLength.TooLong /** * Constructor. * * @since 0.1.0 * * @param ProviderRegistry $registry The provider registry for finding suitable models. * @param Prompt $prompt Optional initial prompt content. * @param EventDispatcherInterface|null $eventDispatcher Optional event dispatcher for lifecycle events. */ // phpcs:enable Generic.Files.LineLength.TooLong public function __construct(ProviderRegistry $registry, $prompt = null, ?EventDispatcherInterface $eventDispatcher = null) { $this->registry = $registry; $this->modelConfig = new ModelConfig(); $this->eventDispatcher = $eventDispatcher; if ($prompt === null) { return; } // Check if it's a list of Messages - set as messages if ($this->isMessagesList($prompt)) { $this->messages = $prompt; return; } // Parse it as a user message $userMessage = $this->parseMessage($prompt, MessageRoleEnum::user()); $this->messages[] = $userMessage; } /** * Creates a deep clone of this builder. * * Clones all mutable state including messages, model configuration, and request options. * Service objects (registry, model, event dispatcher) are intentionally NOT cloned * as they are shared dependencies. * * @since 0.4.2 */ public function __clone() { // Deep clone messages array (Message has __clone) $clonedMessages = []; foreach ($this->messages as $message) { $clonedMessages[] = clone $message; } $this->messages = $clonedMessages; // Clone model config (ModelConfig has __clone) $this->modelConfig = clone $this->modelConfig; // Clone request options if set (contains only primitives) if ($this->requestOptions !== null) { $this->requestOptions = clone $this->requestOptions; } // Note: $registry, $model, and $eventDispatcher are service objects // and are intentionally NOT cloned - they should be shared references. } /** * Adds text to the current message. * * @since 0.1.0 * * @param string $text The text to add. * @return self */ public function withText(string $text): self { $part = new MessagePart($text); $this->appendPartToMessages($part); return $this; } /** * Adds a file to the current message. * * Accepts: * - File object * - URL string (remote file) * - Base64-encoded data string * - Data URI string (data:mime/type;base64,data) * - Local file path string * * @since 0.1.0 * * @param string|File $file The file (File object or string representation). * @param string|null $mimeType The MIME type (optional, ignored if File object provided). * @return self * @throws InvalidArgumentException If the file is invalid or MIME type cannot be determined. */ public function withFile($file, ?string $mimeType = null): self { $file = $file instanceof File ? $file : new File($file, $mimeType); $part = new MessagePart($file); $this->appendPartToMessages($part); return $this; } /** * Adds a function response to the current message. * * @since 0.1.0 * * @param FunctionResponse $functionResponse The function response. * @return self */ public function withFunctionResponse(FunctionResponse $functionResponse): self { $part = new MessagePart($functionResponse); $this->appendPartToMessages($part); return $this; } /** * Adds message parts to the current message. * * @since 0.1.0 * * @param MessagePart ...$parts The message parts to add. * @return self */ public function withMessageParts(MessagePart ...$parts): self { foreach ($parts as $part) { $this->appendPartToMessages($part); } return $this; } /** * Adds conversation history messages. * * Historical messages are prepended to the beginning of the message list, * before the current message being built. * * @since 0.1.0 * * @param Message ...$messages The messages to add to history. * @return self */ public function withHistory(Message ...$messages): self { // Prepend the history messages to the beginning of the messages array $this->messages = array_merge($messages, $this->messages); return $this; } /** * Sets the model to use for generation. * * The model's configuration will be merged with the builder's configuration, * with the builder's configuration taking precedence for any overlapping settings. * * @since 0.1.0 * * @param ModelInterface $model The model to use. * @return self */ public function usingModel(ModelInterface $model): self { $this->model = $model; // Merge model's config with builder's config, with builder's config taking precedence $modelConfigArray = $model->getConfig()->toArray(); $builderConfigArray = $this->modelConfig->toArray(); $mergedConfigArray = array_merge($modelConfigArray, $builderConfigArray); $this->modelConfig = ModelConfig::fromArray($mergedConfigArray); return $this; } /** * Sets preferred models to evaluate in order. * * @since 0.2.0 * * @param string|ModelInterface|array{0:string,1:string} ...$preferredModels The preferred models as model IDs, * model instances, or [provider ID, model ID] tuples. For broader compatibility, it is recommended you specify * only model IDs or model instances, as that will allow for different providers that expose the same model to be * considered. * @return self * * @throws InvalidArgumentException When a preferred model has an invalid type or identifier. */ public function usingModelPreference(...$preferredModels): self { if ($preferredModels === []) { throw new InvalidArgumentException('At least one model preference must be provided.'); } $preferenceKeys = []; foreach ($preferredModels as $preferredModel) { if (is_array($preferredModel)) { // [model identifier, provider ID] tuple if (!array_is_list($preferredModel) || count($preferredModel) !== 2) { throw new InvalidArgumentException('Model preference tuple must contain model identifier and provider ID.'); } [$providerId, $modelId] = $preferredModel; $modelId = $this->normalizePreferenceIdentifier($modelId); $providerId = $this->normalizePreferenceIdentifier($providerId, 'Model preference provider identifiers cannot be empty.'); $preferenceKey = $this->createProviderModelPreferenceKey($providerId, $modelId); } elseif ($preferredModel instanceof ModelInterface) { // Model instance $modelId = $preferredModel->metadata()->getId(); $providerId = $preferredModel->providerMetadata()->getId(); $preferenceKey = $this->createProviderModelPreferenceKey($providerId, $modelId); } elseif (is_string($preferredModel)) { // Model ID $modelId = $this->normalizePreferenceIdentifier($preferredModel); $preferenceKey = $this->createModelPreferenceKey($modelId); } else { // Invalid type throw new InvalidArgumentException('Model preferences must be model identifiers, instances of ModelInterface, ' . 'or provider/model tuples.'); } $preferenceKeys[] = $preferenceKey; } $this->modelPreferenceKeys = $preferenceKeys; return $this; } /** * Sets the model configuration. * * Merges the provided configuration with the builder's configuration, * with builder configuration taking precedence. * * @since 0.1.0 * * @param ModelConfig $config The model configuration to merge. * @return self */ public function usingModelConfig(ModelConfig $config): self { // Convert both configs to arrays $builderConfigArray = $this->modelConfig->toArray(); $providedConfigArray = $config->toArray(); // Merge arrays with builder config taking precedence $mergedArray = array_merge($providedConfigArray, $builderConfigArray); // Create new config from merged array $this->modelConfig = ModelConfig::fromArray($mergedArray); return $this; } /** * Sets the provider to use for generation. * * @since 0.1.0 * * @param string $providerIdOrClassName The provider ID or class name. * @return self */ public function usingProvider(string $providerIdOrClassName): self { $this->providerIdOrClassName = $providerIdOrClassName; return $this; } /** * Sets the system instruction. * * System instructions are stored in the model configuration and guide * the AI model's behavior throughout the conversation. * * @since 0.1.0 * * @param string $systemInstruction The system instruction text. * @return self */ public function usingSystemInstruction(string $systemInstruction): self { $this->modelConfig->setSystemInstruction($systemInstruction); return $this; } /** * Sets the maximum number of tokens to generate. * * @since 0.1.0 * * @param int $maxTokens The maximum number of tokens. * @return self */ public function usingMaxTokens(int $maxTokens): self { $this->modelConfig->setMaxTokens($maxTokens); return $this; } /** * Sets the temperature for generation. * * @since 0.1.0 * * @param float $temperature The temperature value. * @return self */ public function usingTemperature(float $temperature): self { $this->modelConfig->setTemperature($temperature); return $this; } /** * Sets the top-p value for generation. * * @since 0.1.0 * * @param float $topP The top-p value. * @return self */ public function usingTopP(float $topP): self { $this->modelConfig->setTopP($topP); return $this; } /** * Sets the top-k value for generation. * * @since 0.1.0 * * @param int $topK The top-k value. * @return self */ public function usingTopK(int $topK): self { $this->modelConfig->setTopK($topK); return $this; } /** * Sets stop sequences for generation. * * @since 0.1.0 * * @param string ...$stopSequences The stop sequences. * @return self */ public function usingStopSequences(string ...$stopSequences): self { $this->modelConfig->setStopSequences($stopSequences); return $this; } /** * Sets the number of candidates to generate. * * @since 0.1.0 * * @param int $candidateCount The number of candidates. * @return self */ public function usingCandidateCount(int $candidateCount): self { $this->modelConfig->setCandidateCount($candidateCount); return $this; } /** * Sets the function declarations available to the model. * * @since 0.1.0 * * @param FunctionDeclaration ...$functionDeclarations The function declarations. * @return self */ public function usingFunctionDeclarations(FunctionDeclaration ...$functionDeclarations): self { $this->modelConfig->setFunctionDeclarations($functionDeclarations); return $this; } /** * Sets the presence penalty for generation. * * @since 0.1.0 * * @param float $presencePenalty The presence penalty value. * @return self */ public function usingPresencePenalty(float $presencePenalty): self { $this->modelConfig->setPresencePenalty($presencePenalty); return $this; } /** * Sets the frequency penalty for generation. * * @since 0.1.0 * * @param float $frequencyPenalty The frequency penalty value. * @return self */ public function usingFrequencyPenalty(float $frequencyPenalty): self { $this->modelConfig->setFrequencyPenalty($frequencyPenalty); return $this; } /** * Sets the web search configuration. * * @since 0.1.0 * * @param WebSearch $webSearch The web search configuration. * @return self */ public function usingWebSearch(WebSearch $webSearch): self { $this->modelConfig->setWebSearch($webSearch); return $this; } /** * Sets the request options for HTTP transport. * * @since 0.3.0 * * @param RequestOptions $requestOptions The request options. * @return self */ public function usingRequestOptions(RequestOptions $requestOptions): self { $this->requestOptions = $requestOptions; return $this; } /** * Sets the top log probabilities configuration. * * If $topLogprobs is null, enables log probabilities. * If $topLogprobs has a value, enables log probabilities and sets the number of top log probabilities to return. * * @since 0.1.0 * * @param int|null $topLogprobs The number of top log probabilities to return, or null to enable log probabilities. * @return self */ public function usingTopLogprobs(?int $topLogprobs = null): self { // Always enable log probabilities $this->modelConfig->setLogprobs(\true); // If a specific number is provided, set it if ($topLogprobs !== null) { $this->modelConfig->setTopLogprobs($topLogprobs); } return $this; } /** * Sets the output MIME type. * * @since 0.1.0 * * @param string $mimeType The MIME type. * @return self */ public function asOutputMimeType(string $mimeType): self { $this->modelConfig->setOutputMimeType($mimeType); return $this; } /** * Sets the output schema. * * @since 0.1.0 * * @param array<string, mixed> $schema The output schema. * @return self */ public function asOutputSchema(array $schema): self { $this->modelConfig->setOutputSchema($schema); return $this; } /** * Sets the output modalities. * * @since 0.1.0 * * @param ModalityEnum ...$modalities The output modalities. * @return self */ public function asOutputModalities(ModalityEnum ...$modalities): self { $this->modelConfig->setOutputModalities($modalities); return $this; } /** * Sets the output file type. * * @since 0.1.0 * * @param FileTypeEnum $fileType The output file type. * @return self */ public function asOutputFileType(FileTypeEnum $fileType): self { $this->modelConfig->setOutputFileType($fileType); return $this; } /** * Sets the output media orientation. * * @since 1.3.0 * * @param MediaOrientationEnum $orientation The output media orientation. * @return self */ public function asOutputMediaOrientation(MediaOrientationEnum $orientation): self { $this->modelConfig->setOutputMediaOrientation($orientation); return $this; } /** * Sets the output media aspect ratio. * * If set, this supersedes the output media orientation, as it is a more * specific configuration. * * @since 1.3.0 * * @param string $aspectRatio The aspect ratio (e.g. "16:9", "3:2"). * @return self */ public function asOutputMediaAspectRatio(string $aspectRatio): self { $this->modelConfig->setOutputMediaAspectRatio($aspectRatio); return $this; } /** * Sets the output speech voice. * * @since 1.3.0 * * @param string $voice The output speech voice. * @return self */ public function asOutputSpeechVoice(string $voice): self { $this->modelConfig->setOutputSpeechVoice($voice); return $this; } /** * Configures the prompt for JSON response output. * * @since 0.1.0 * * @param array<string, mixed>|null $schema Optional JSON schema. * @return self */ public function asJsonResponse(?array $schema = null): self { $this->asOutputMimeType('application/json'); if ($schema !== null) { $this->asOutputSchema($schema); } return $this; } /** * Infers the capability from configured output modalities. * * @since 0.1.0 * * @return CapabilityEnum The inferred capability. * @throws RuntimeException If the output modality is not supported. */ private function inferCapabilityFromOutputModalities(): CapabilityEnum { // Get the configured output modalities $outputModalities = $this->modelConfig->getOutputModalities(); // Default to text if no output modality is specified if ($outputModalities === null || empty($outputModalities)) { return CapabilityEnum::textGeneration(); } // Multi-modal output (multiple modalities) defaults to text generation. This is temporary // as a multi-modal interface will be implemented in the future. if (count($outputModalities) > 1) { return CapabilityEnum::textGeneration(); } // Infer capability from single output modality $outputModality = $outputModalities[0]; if ($outputModality->isText()) { return CapabilityEnum::textGeneration(); } elseif ($outputModality->isImage()) { return CapabilityEnum::imageGeneration(); } elseif ($outputModality->isAudio()) { return CapabilityEnum::speechGeneration(); } elseif ($outputModality->isVideo()) { return CapabilityEnum::videoGeneration(); } else { // For unsupported modalities, provide a clear error message throw new RuntimeException(sprintf('Output modality "%s" is not yet supported.', $outputModality->value)); } } /** * Infers the capability from a model's implemented interfaces. * * @since 0.1.0 * * @param ModelInterface $model The model to infer capability from. * @return CapabilityEnum|null The inferred capability, or null if none can be inferred. */ private function inferCapabilityFromModelInterfaces(ModelInterface $model): ?CapabilityEnum { // Check model interfaces in order of preference if ($model instanceof TextGenerationModelInterface) { return CapabilityEnum::textGeneration(); } if ($model instanceof ImageGenerationModelInterface) { return CapabilityEnum::imageGeneration(); } if ($model instanceof TextToSpeechConversionModelInterface) { return CapabilityEnum::textToSpeechConversion(); } if ($model instanceof SpeechGenerationModelInterface) { return CapabilityEnum::speechGeneration(); } if ($model instanceof VideoGenerationModelInterface) { return CapabilityEnum::videoGeneration(); } // No supported interface found return null; } /** * Checks if the current prompt is supported by the selected model. * * @since 0.1.0 * @since 0.3.0 Method visibility changed to public. * * @param CapabilityEnum|null $capability Optional capability to check support for. * @return bool True if supported, false otherwise. */ public function isSupported(?CapabilityEnum $capability = null): bool { // If no intended capability provided, infer from output modalities if ($capability === null) { // First try to infer from a specific model if one is set if ($this->model !== null) { $inferredCapability = $this->inferCapabilityFromModelInterfaces($this->model); if ($inferredCapability !== null) { $capability = $inferredCapability; } } // If still no capability, infer from output modalities if ($capability === null) { $capability = $this->inferCapabilityFromOutputModalities(); } } // Build requirements with the specified capability $requirements = ModelRequirements::fromPromptData($capability, $this->messages, $this->modelConfig); // If the model has been set, check if it meets the requirements if ($this->model !== null) { return $requirements->areMetBy($this->model->metadata()); } try { // Check if any models support these requirements $models = $this->registry->findModelsMetadataForSupport($requirements); return !empty($models); } catch (InvalidArgumentException $e) { // No models support the requirements return \false; } } /** * Checks if the prompt is supported for text generation. * * @since 0.1.0 * * @return bool True if text generation is supported. */ public function isSupportedForTextGeneration(): bool { return $this->isSupported(CapabilityEnum::textGeneration()); } /** * Checks if the prompt is supported for image generation. * * @since 0.1.0 * * @return bool True if image generation is supported. */ public function isSupportedForImageGeneration(): bool { return $this->isSupported(CapabilityEnum::imageGeneration()); } /** * Checks if the prompt is supported for text to speech conversion. * * @since 0.1.0 * * @return bool True if text to speech conversion is supported. */ public function isSupportedForTextToSpeechConversion(): bool { return $this->isSupported(CapabilityEnum::textToSpeechConversion()); } /** * Checks if the prompt is supported for video generation. * * @since 0.1.0 * * @return bool True if video generation is supported. */ public function isSupportedForVideoGeneration(): bool { return $this->isSupported(CapabilityEnum::videoGeneration()); } /** * Checks if the prompt is supported for speech generation. * * @since 0.1.0 * * @return bool True if speech generation is supported. */ public function isSupportedForSpeechGeneration(): bool { return $this->isSupported(CapabilityEnum::speechGeneration()); } /** * Checks if the prompt is supported for music generation. * * @since 0.1.0 * * @return bool True if music generation is supported. */ public function isSupportedForMusicGeneration(): bool { return $this->isSupported(CapabilityEnum::musicGeneration()); } /** * Checks if the prompt is supported for embedding generation. * * @since 0.1.0 * * @return bool True if embedding generation is supported. */ public function isSupportedForEmbeddingGeneration(): bool { return $this->isSupported(CapabilityEnum::embeddingGeneration()); } /** * Generates a result from the prompt. * * This is the primary execution method that generates a result (containing * potentially multiple candidates) based on the specified capability or * the configured output modality. * * @since 0.1.0 * * @param CapabilityEnum|null $capability Optional capability to use for generation. * If null, capability is inferred from output modality. * @return GenerativeAiResult The generated result containing candidates. * @throws InvalidArgumentException If the prompt or model validation fails. * @throws RuntimeException If the model doesn't support the required capability. */ public function generateResult(?CapabilityEnum $capability = null): GenerativeAiResult { $this->validateMessages(); // If capability is not provided, infer it if ($capability === null) { // First try to infer from a specific model if one is set if ($this->model !== null) { $inferredCapability = $this->inferCapabilityFromModelInterfaces($this->model); if ($inferredCapability !== null) { $capability = $inferredCapability; } } // If still no capability, infer from output modalities if ($capability === null) { $capability = $this->inferCapabilityFromOutputModalities(); } } $model = $this->getConfiguredModel($capability); // Dispatch BeforeGenerateResultEvent $this->dispatchEvent(new BeforeGenerateResultEvent($this->messages, $model, $capability)); // Route to the appropriate generation method based on capability $result = $this->executeModelGeneration($model, $capability, $this->messages); // Dispatch AfterGenerateResultEvent $this->dispatchEvent(new AfterGenerateResultEvent($this->messages, $model, $capability, $result)); return $result; } /** * Executes the model generation based on capability. * * @since 0.4.0 * * @param ModelInterface $model The model to use for generation. * @param CapabilityEnum $capability The capability to use. * @param list<Message> $messages The messages to send. * @return GenerativeAiResult The generated result. * @throws RuntimeException If the model doesn't support the required capability. */ private function executeModelGeneration(ModelInterface $model, CapabilityEnum $capability, array $messages): GenerativeAiResult { if ($capability->isTextGeneration()) { if (!$model instanceof TextGenerationModelInterface) { throw new RuntimeException(sprintf('Model "%s" does not support text generation.', $model->metadata()->getId())); } return $model->generateTextResult($messages); } if ($capability->isImageGeneration()) { if (!$model instanceof ImageGenerationModelInterface) { throw new RuntimeException(sprintf('Model "%s" does not support image generation.', $model->metadata()->getId())); } return $model->generateImageResult($messages); } if ($capability->isTextToSpeechConversion()) { if (!$model instanceof TextToSpeechConversionModelInterface) { throw new RuntimeException(sprintf('Model "%s" does not support text-to-speech conversion.', $model->metadata()->getId())); } return $model->convertTextToSpeechResult($messages); } if ($capability->isSpeechGeneration()) { if (!$model instanceof SpeechGenerationModelInterface) { throw new RuntimeException(sprintf('Model "%s" does not support speech generation.', $model->metadata()->getId())); } return $model->generateSpeechResult($messages); } if ($capability->isVideoGeneration()) { if (!$model instanceof VideoGenerationModelInterface) { throw new RuntimeException(sprintf('Model "%s" does not support video generation.', $model->metadata()->getId())); } return $model->generateVideoResult($messages); } // TODO: Add support for other capabilities when interfaces are available throw new RuntimeException(sprintf('Capability "%s" is not yet supported for generation.', $capability->value)); } /** * Generates a text result from the prompt. * * @since 0.1.0 * * @return GenerativeAiResult The generated result containing text candidates. * @throws InvalidArgumentException If the prompt or model validation fails. * @throws RuntimeException If the model doesn't support text generation. */ public function generateTextResult(): GenerativeAiResult { // Include text in output modalities $this->includeOutputModalities(ModalityEnum::text()); // Generate and return the result with text generation capability return $this->generateResult(CapabilityEnum::textGeneration()); } /** * Generates an image result from the prompt. * * @since 0.1.0 * * @return GenerativeAiResult The generated result containing image candidates. * @throws InvalidArgumentException If the prompt or model validation fails. * @throws RuntimeException If the model doesn't support image generation. */ public function generateImageResult(): GenerativeAiResult { // Include image in output modalities $this->includeOutputModalities(ModalityEnum::image()); // Generate and return the result with image generation capability return $this->generateResult(CapabilityEnum::imageGeneration()); } /** * Generates a speech result from the prompt. * * @since 0.1.0 * * @return GenerativeAiResult The generated result containing speech audio candidates. * @throws InvalidArgumentException If the prompt or model validation fails. * @throws RuntimeException If the model doesn't support speech generation. */ public function generateSpeechResult(): GenerativeAiResult { // Include audio in output modalities $this->includeOutputModalities(ModalityEnum::audio()); // Generate and return the result with speech generation capability return $this->generateResult(CapabilityEnum::speechGeneration()); } /** * Converts text to speech and returns the result. * * @since 0.1.0 * * @return GenerativeAiResult The generated result containing speech audio candidates. * @throws InvalidArgumentException If the prompt or model validation fails. * @throws RuntimeException If the model doesn't support text-to-speech conversion. */ public function convertTextToSpeechResult(): GenerativeAiResult { // Include audio in output modalities $this->includeOutputModalities(ModalityEnum::audio()); // Generate and return the result with text-to-speech conversion capability return $this->generateResult(CapabilityEnum::textToSpeechConversion()); } /** * Generates a video result from the prompt. * * @since 1.3.0 * * @return GenerativeAiResult The generated result containing video candidates. * @throws InvalidArgumentException If the prompt or model validation fails. * @throws RuntimeException If the model doesn't support video generation. */ public function generateVideoResult(): GenerativeAiResult { // Include video in output modalities $this->includeOutputModalities(ModalityEnum::video()); // Generate and return the result with video generation capability return $this->generateResult(CapabilityEnum::videoGeneration()); } /** * Generates text from the prompt. * * @since 0.1.0 * * @return string The generated text. * @throws InvalidArgumentException If the prompt or model validation fails. */ public function generateText(): string { return $this->generateTextResult()->toText(); } /** * Generates multiple text candidates from the prompt. * * @since 0.1.0 * * @param int|null $candidateCount The number of candidates to generate. * @return list<string> The generated texts. * @throws InvalidArgumentException If the prompt or model validation fails. */ public function generateTexts(?int $candidateCount = null): array { if ($candidateCount !== null) { $this->usingCandidateCount($candidateCount); } // Generate text result return $this->generateTextResult()->toTexts(); } /** * Generates an image from the prompt. * * @since 0.1.0 * * @return File The generated image file. * @throws InvalidArgumentException If the prompt or model validation fails. * @throws RuntimeException If no image is generated. */ public function generateImage(): File { return $this->generateImageResult()->toFile(); } /** * Generates multiple images from the prompt. * * @since 0.1.0 * * @param int|null $candidateCount The number of images to generate. * @return list<File> The generated image files. * @throws InvalidArgumentException If the prompt or model validation fails. * @throws RuntimeException If no images are generated. */ public function generateImages(?int $candidateCount = null): array { if ($candidateCount !== null) { $this->usingCandidateCount($candidateCount); } return $this->generateImageResult()->toFiles(); } /** * Converts text to speech. * * @since 0.1.0 * * @return File The generated speech audio file. * @throws InvalidArgumentException If the prompt or model validation fails. * @throws RuntimeException If no audio is generated. */ public function convertTextToSpeech(): File { return $this->convertTextToSpeechResult()->toFile(); } /** * Converts text to multiple speech outputs. * * @since 0.1.0 * * @param int|null $candidateCount The number of speech outputs to generate. * @return list<File> The generated speech audio files. * @throws InvalidArgumentException If the prompt or model validation fails. * @throws RuntimeException If no audio is generated. */ public function convertTextToSpeeches(?int $candidateCount = null): array { if ($candidateCount !== null) { $this->usingCandidateCount($candidateCount); } return $this->convertTextToSpeechResult()->toFiles(); } /** * Generates speech from the prompt. * * @since 0.1.0 * * @return File The generated speech audio file. * @throws InvalidArgumentException If the prompt or model validation fails. * @throws RuntimeException If no audio is generated. */ public function generateSpeech(): File { return $this->generateSpeechResult()->toFile(); } /** * Generates multiple speech outputs from the prompt. * * @since 0.1.0 * * @param int|null $candidateCount The number of speech outputs to generate. * @return list<File> The generated speech audio files. * @throws InvalidArgumentException If the prompt or model validation fails. * @throws RuntimeException If no audio is generated. */ public function generateSpeeches(?int $candidateCount = null): array { if ($candidateCount !== null) { $this->usingCandidateCount($candidateCount); } return $this->generateSpeechResult()->toFiles(); } /** * Generates a video from the prompt. * * @since 1.3.0 * * @return File The generated video file. * @throws InvalidArgumentException If the prompt or model validation fails. * @throws RuntimeException If no video is generated. */ public function generateVideo(): File { return $this->generateVideoResult()->toFile(); } /** * Generates multiple videos from the prompt. * * @since 1.3.0 * * @param int|null $candidateCount The number of videos to generate. * @return list<File> The generated video files. * @throws InvalidArgumentException If the prompt or model validation fails. * @throws RuntimeException If no videos are generated. */ public function generateVideos(?int $candidateCount = null): array { if ($candidateCount !== null) { $this->usingCandidateCount($candidateCount); } return $this->generateVideoResult()->toFiles(); } /** * Appends a MessagePart to the messages array. * * If the last message has a user role, the part is added to it. * Otherwise, a new UserMessage is created with the part. * * @since 0.1.0 * * @param MessagePart $part The part to append. * @return void */ protected function appendPartToMessages(MessagePart $part): void { $lastMessage = end($this->messages); if ($lastMessage instanceof Message && $lastMessage->getRole()->isUser()) { // Replace the last message with a new one containing the appended part array_pop($this->messages); $this->messages[] = $lastMessage->withPart($part); return; } // Create new UserMessage with the part $this->messages[] = new UserMessage([$part]); } /** * Gets the model to use for generation. * * If a model has been explicitly set, validates it meets requirements and returns it. * Otherwise, finds a suitable model based on the prompt requirements. * * @since 0.1.0 * * @param CapabilityEnum $capability The capability the model will be using. * @return ModelInterface The model to use. * @throws InvalidArgumentException If no suitable model is found or set model doesn't meet requirements. */ private function getConfiguredModel(CapabilityEnum $capability): ModelInterface { $requirements = ModelRequirements::fromPromptData($capability, $this->messages, $this->modelConfig); if ($this->model !== null) { // Explicit model was provided via usingModel(); just update config and bind dependencies. $model = $this->model; $model->setConfig($this->modelConfig); $this->registry->bindModelDependencies($model); $this->bindModelRequestOptions($model); return $model; } // Retrieve the candidate models map which satisfies the requirements. $candidateMap = $this->getCandidateModelsMap($requirements); if (empty($candidateMap)) { $message = sprintf('No models found that support %s for this prompt.', $capability->value); if ($this->providerIdOrClassName !== null) { $message = sprintf('No models found for provider "%s" that support %s for this prompt.', $this->providerIdOrClassName, $capability->value); } throw new InvalidArgumentException($message); } // Check if any preferred models match the candidates, in priority order. if (!empty($this->modelPreferenceKeys)) { // Find preferences that match available candidates, preserving preference order. $matchingPreferences = array_intersect_key(array_flip($this->modelPreferenceKeys), $candidateMap); if (!empty($matchingPreferences)) { // Get the first matching preference key $firstMatchKey = key($matchingPreferences); [$providerId, $modelId] = $candidateMap[$firstMatchKey]; $model = $this->registry->getProviderModel($providerId, $modelId, $this->modelConfig); $this->bindModelRequestOptions($model); return $model; } } // No preference matched; fall back to the first candidate discovered. [$providerId, $modelId] = reset($candidateMap); $model = $this->registry->getProviderModel($providerId, $modelId, $this->modelConfig); $this->bindModelRequestOptions($model); return $model; } /** * Binds configured request options to the model if present and supported. * * Request options are only applicable to API-based models that make HTTP requests. * * @since 0.3.0 * * @param ModelInterface $model The model to bind request options to. * @return void */ private function bindModelRequestOptions(ModelInterface $model): void { if ($this->requestOptions !== null && $model instanceof ApiBasedModelInterface) { $model->setRequestOptions($this->requestOptions); } } /** * Builds a map of candidate models that satisfy the requirements for efficient lookup. * * @since 0.2.0 * * @param ModelRequirements $requirements The requirements derived from the prompt. * @return array<string, array{0:string,1:string}> Map of preference keys to [providerId, modelId] tuples. */ private function getCandidateModelsMap(ModelRequirements $requirements): array { if ($this->providerIdOrClassName === null) { // No provider locked in, gather all models across providers that meet requirements. $providerModelsMetadata = $this->registry->findModelsMetadataForSupport($requirements); $candidateMap = []; foreach ($providerModelsMetadata as $providerModels) { $providerId = $providerModels->getProvider()->getId(); $providerMap = $this->generateMapFromCandidates($providerId, $providerModels->getModels()); // Use + operator to merge, preserving keys from $candidateMap (first provider wins for model-only keys) $candidateMap = $candidateMap + $providerMap; } return $candidateMap; } // Provider set, only consider models from that provider. $modelsMetadata = $this->registry->findProviderModelsMetadataForSupport($this->providerIdOrClassName, $requirements); // Ensure we pass the provider ID, not the class name $providerId = $this->registry->getProviderId($this->providerIdOrClassName); return $this->generateMapFromCandidates($providerId, $modelsMetadata); } /** * Generates a candidate map from model metadata with both provider-specific and model-only keys. * * @since 0.2.0 * * @param string $providerId The provider ID. * @param list<ModelMetadata> $modelsMetadata The models metadata to map. * @return array<string, array{0:string,1:string}> Map of preference keys to [providerId, modelId] tuples. */ private function generateMapFromCandidates(string $providerId, array $modelsMetadata): array { $map = []; foreach ($modelsMetadata as $modelMetadata) { $modelId = $modelMetadata->getId(); // Add provider-specific key $providerModelKey = $this->createProviderModelPreferenceKey($providerId, $modelId); $map[$providerModelKey] = [$providerId, $modelId]; // Add model-only key $modelKey = $this->createModelPreferenceKey($modelId); $map[$modelKey] = [$providerId, $modelId]; } return $map; } /** * Normalizes and validates a preference identifier string. * * @since 0.2.0 * * @param mixed $value The value to normalize. * @param string $emptyMessage The message for empty or invalid values. * @return string The normalized identifier. * * @throws InvalidArgumentException If the value is not a non-empty string. */ private function normalizePreferenceIdentifier($value, string $emptyMessage = 'Model preference identifiers cannot be empty.'): string { if (!is_string($value)) { throw new InvalidArgumentException($emptyMessage); } $trimmed = trim($value); if ($trimmed === '') { throw new InvalidArgumentException($emptyMessage); } return $trimmed; } /** * Creates a preference key for a provider/model combination. * * @since 0.2.0 * * @param string $providerId The provider identifier. * @param string $modelId The model identifier. * @return string The generated preference key. */ private function createProviderModelPreferenceKey(string $providerId, string $modelId): string { return 'providerModel::' . $providerId . '::' . $modelId; } /** * Creates a preference key for a model identifier. * * @since 0.2.0 * * @param string $modelId The model identifier. * @return string The generated preference key. */ private function createModelPreferenceKey(string $modelId): string { return 'model::' . $modelId; } /** * Parses various input types into a Message with the given role. * * @since 0.1.0 * * @param mixed $input The input to parse. * @param MessageRoleEnum $defaultRole The role for the message if not specified by input. * @return Message The parsed message. * @throws InvalidArgumentException If the input type is not supported or results in empty message. */ private function parseMessage($input, MessageRoleEnum $defaultRole): Message { // Handle Message input directly if ($input instanceof Message) { return $input; } // Handle single MessagePart if ($input instanceof MessagePart) { return new Message($defaultRole, [$input]); } // Handle string input if (is_string($input)) { if (trim($input) === '') { throw new InvalidArgumentException('Cannot create a message from an empty string.'); } return new Message($defaultRole, [new MessagePart($input)]); } // Handle array input if (!is_array($input)) { throw new InvalidArgumentException('Input must be a string, MessagePart, MessagePartArrayShape, ' . 'a list of string|MessagePart|MessagePartArrayShape, or a Message instance.'); } // Handle MessageArrayShape input if (Message::isArrayShape($input)) { return Message::fromArray($input); } // Check if it's a MessagePartArrayShape if (MessagePart::isArrayShape($input)) { return new Message($defaultRole, [MessagePart::fromArray($input)]); } // It should be a list of string|MessagePart|MessagePartArrayShape if (!array_is_list($input)) { throw new InvalidArgumentException('Array input must be a list array.'); } // Empty array check if (empty($input)) { throw new InvalidArgumentException('Cannot create a message from an empty array.'); } $parts = []; foreach ($input as $item) { if (is_string($item)) { $parts[] = new MessagePart($item); } elseif ($item instanceof MessagePart) { $parts[] = $item; } elseif (is_array($item) && MessagePart::isArrayShape($item)) { $parts[] = MessagePart::fromArray($item); } else { throw new InvalidArgumentException('Array items must be strings, MessagePart instances, or MessagePartArrayShape.'); } } return new Message($defaultRole, $parts); } /** * Validates the messages array for prompt generation. * * Ensures that: * - The first message is a user message * - The last message is a user message * - The last message has parts * * @since 0.1.0 * * @return void * @throws InvalidArgumentException If validation fails. */ private function validateMessages(): void { if (empty($this->messages)) { throw new InvalidArgumentException('Cannot generate from an empty prompt. Add content using withText() or similar methods.'); } $firstMessage = reset($this->messages); if (!$firstMessage->getRole()->isUser()) { throw new InvalidArgumentException('The first message must be from a user role, not from ' . $firstMessage->getRole()->value); } $lastMessage = end($this->messages); if (!$lastMessage->getRole()->isUser()) { throw new InvalidArgumentException('The last message must be from a user role, not from ' . $lastMessage->getRole()->value); } if (empty($lastMessage->getParts())) { throw new InvalidArgumentException('The last message must have content parts. Add content using withText() or similar methods.'); } } /** * Checks if the value is a list of Message objects. * * @since 0.1.0 * * @param mixed $value The value to check. * @return bool True if the value is a list of Message objects. * * @phpstan-assert-if-true list<Message> $value */ private function isMessagesList($value): bool { if (!is_array($value) || empty($value) || !array_is_list($value)) { return \false; } // Check if all items are Messages foreach ($value as $item) { if (!$item instanceof Message) { return \false; } } return \true; } /** * Includes output modalities if not already present. * * Adds the given modalities to the output modalities list if they're not * already included. If output modalities is null, initializes it with * the given modalities. * * @since 0.1.0 * * @param ModalityEnum ...$modalities The modalities to include. * @return void */ private function includeOutputModalities(ModalityEnum ...$modalities): void { $existing = $this->modelConfig->getOutputModalities(); // Initialize if null if ($existing === null) { $this->modelConfig->setOutputModalities($modalities); return; } // Build a set of existing modality values for O(1) lookup $existingValues = []; foreach ($existing as $existingModality) { $existingValues[$existingModality->value] = \true; } // Add new modalities that don't exist $toAdd = []; foreach ($modalities as $modality) { if (!isset($existingValues[$modality->value])) { $toAdd[] = $modality; } } // Update if we have new modalities to add if (!empty($toAdd)) { $this->modelConfig->setOutputModalities(array_merge($existing, $toAdd)); } } /** * Dispatches an event if an event dispatcher is registered. * * @since 0.4.0 * * @param object $event The event to dispatch. * @return void */ private function dispatchEvent(object $event): void { if ($this->eventDispatcher !== null) { $this->eventDispatcher->dispatch($event); } } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Builders/MessageBuilder.php�������������������������������������������������������������������������0000644�����������������00000014672�15250554011�0011731 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Builders; use InvalidArgumentException; use WordPress\AiClient\Files\DTO\File; use WordPress\AiClient\Messages\DTO\Message; use WordPress\AiClient\Messages\DTO\MessagePart; use WordPress\AiClient\Messages\Enums\MessageRoleEnum; use WordPress\AiClient\Tools\DTO\FunctionCall; use WordPress\AiClient\Tools\DTO\FunctionResponse; /** * Fluent builder for constructing AI messages. * * This class provides a fluent interface for building messages with various * content types including text, files, function calls, and function responses. * * @since 0.2.0 * * @phpstan-import-type MessagePartArrayShape from MessagePart * * @phpstan-type Input string|MessagePart|MessagePartArrayShape|File|FunctionCall|FunctionResponse|null */ class MessageBuilder { /** * @var MessageRoleEnum|null The role of the message sender. */ protected ?MessageRoleEnum $role = null; /** * @var list<MessagePart> The parts that make up the message. */ protected array $parts = []; /** * Constructor. * * @since 0.2.0 * * @param Input $input Optional initial content. * @param MessageRoleEnum|null $role Optional role. */ public function __construct($input = null, ?MessageRoleEnum $role = null) { $this->role = $role; if ($input === null) { return; } // Handle different input types if ($input instanceof MessagePart) { $this->parts[] = $input; } elseif (is_string($input)) { $this->withText($input); } elseif ($input instanceof File) { $this->withFile($input); } elseif ($input instanceof FunctionCall) { $this->withFunctionCall($input); } elseif ($input instanceof FunctionResponse) { $this->withFunctionResponse($input); } elseif (is_array($input) && MessagePart::isArrayShape($input)) { $this->parts[] = MessagePart::fromArray($input); } else { throw new InvalidArgumentException('Input must be a string, MessagePart, MessagePartArrayShape, File, FunctionCall, or FunctionResponse.'); } } /** * Creates a deep clone of this builder. * * Clones all MessagePart objects in the parts array to ensure * the cloned builder is independent of the original. * * @since 0.4.2 */ public function __clone() { // Deep clone parts array (MessagePart has __clone) $clonedParts = []; foreach ($this->parts as $part) { $clonedParts[] = clone $part; } $this->parts = $clonedParts; // Note: $role is an enum value object and can be safely shared } /** * Sets the role of the message sender. * * @since 0.2.0 * * @param MessageRoleEnum $role The role to set. * @return self */ public function usingRole(MessageRoleEnum $role): self { $this->role = $role; return $this; } /** * Sets the role to user. * * @since 0.2.0 * * @return self */ public function usingUserRole(): self { return $this->usingRole(MessageRoleEnum::user()); } /** * Sets the role to model. * * @since 0.2.0 * * @return self */ public function usingModelRole(): self { return $this->usingRole(MessageRoleEnum::model()); } /** * Adds text content to the message. * * @since 0.2.0 * * @param string $text The text to add. * @return self * @throws InvalidArgumentException If the text is empty. */ public function withText(string $text): self { if (trim($text) === '') { throw new InvalidArgumentException('Text content cannot be empty.'); } $this->parts[] = new MessagePart($text); return $this; } /** * Adds a file to the message. * * Accepts: * - File object * - URL string (remote file) * - Base64-encoded data string * - Data URI string (data:mime/type;base64,data) * - Local file path string * * @since 0.2.0 * * @param string|File $file The file to add. * @param string|null $mimeType Optional MIME type (ignored if File object provided). * @return self * @throws InvalidArgumentException If the file is invalid. */ public function withFile($file, ?string $mimeType = null): self { $file = $file instanceof File ? $file : new File($file, $mimeType); $this->parts[] = new MessagePart($file); return $this; } /** * Adds a function call to the message. * * @since 0.2.0 * * @param FunctionCall $functionCall The function call to add. * @return self */ public function withFunctionCall(FunctionCall $functionCall): self { $this->parts[] = new MessagePart($functionCall); return $this; } /** * Adds a function response to the message. * * @since 0.2.0 * * @param FunctionResponse $functionResponse The function response to add. * @return self */ public function withFunctionResponse(FunctionResponse $functionResponse): self { $this->parts[] = new MessagePart($functionResponse); return $this; } /** * Adds multiple message parts to the message. * * @since 0.2.0 * * @param MessagePart ...$parts The message parts to add. * @return self */ public function withMessageParts(MessagePart ...$parts): self { foreach ($parts as $part) { $this->parts[] = $part; } return $this; } /** * Builds and returns the Message object. * * @since 0.2.0 * * @return Message The built message. * @throws InvalidArgumentException If the message validation fails. */ public function get(): Message { if (empty($this->parts)) { throw new InvalidArgumentException('Cannot build an empty message. Add content using withText() or similar methods.'); } if ($this->role === null) { throw new InvalidArgumentException('Cannot build a message with no role. Set a role using usingRole() or similar methods.'); } // At this point, we've validated that $this->role is not null /** @var MessageRoleEnum $role */ $role = $this->role; return new Message($role, $this->parts); } } ����������������������������������������������������������������������Providers/DTO/error_log�����������������������������������������������������������������������������0000644�����������������00000022220�15250554011�0011060 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������[15-Jul-2026 16:16:46 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php:27 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php on line 27 [15-Jul-2026 16:16:47 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php:32 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php on line 32 [24-Jul-2026 12:15:34 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php:27 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php on line 27 [24-Jul-2026 12:15:35 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php:32 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php on line 32 [25-Jul-2026 05:33:21 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php:32 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php on line 32 [25-Jul-2026 05:33:21 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php:27 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php on line 27 [04-Aug-2026 23:27:27 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php:32 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php on line 32 [04-Aug-2026 23:27:27 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php:27 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php on line 27 [13-Aug-2026 01:33:16 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php:32 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php on line 32 [13-Aug-2026 01:33:16 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php:27 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php on line 27 [13-Aug-2026 13:00:02 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php:27 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php on line 27 [13-Aug-2026 13:00:02 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php:32 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php on line 32 [19-Aug-2026 21:32:05 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php:27 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php on line 27 [19-Aug-2026 21:32:05 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php:32 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php on line 32 [19-Aug-2026 22:05:45 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php:27 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php on line 27 [19-Aug-2026 22:05:45 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php:32 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php on line 32 [19-Aug-2026 23:24:12 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php:32 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php on line 32 [19-Aug-2026 23:24:13 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php:27 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php on line 27 [03-Sep-2026 06:51:08 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php:32 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php on line 32 [03-Sep-2026 06:51:15 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php:27 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php on line 27 [03-Sep-2026 18:00:56 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php:27 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php on line 27 [03-Sep-2026 20:36:35 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php:32 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php on line 32 [04-Sep-2026 22:38:23 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php:27 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php on line 27 [04-Sep-2026 22:38:35 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php:32 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php on line 32 ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/DTO/ProviderModelsMetadata.php������������������������������������������������������������0000644�����������������00000010144�15250554011�0014255 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Providers\Models\DTO\ModelMetadata; /** * Represents metadata about a provider and its available models. * * This class combines provider information with the models that * the provider offers, facilitating model discovery and selection. * * @since 0.1.0 * * @phpstan-import-type ProviderMetadataArrayShape from ProviderMetadata * @phpstan-import-type ModelMetadataArrayShape from ModelMetadata * * @phpstan-type ProviderModelsMetadataArrayShape array{ * provider: ProviderMetadataArrayShape, * models: list<ModelMetadataArrayShape> * } * * @extends AbstractDataTransferObject<ProviderModelsMetadataArrayShape> */ class ProviderModelsMetadata extends AbstractDataTransferObject { public const KEY_PROVIDER = 'provider'; public const KEY_MODELS = 'models'; /** * @var ProviderMetadata The provider metadata. */ protected \WordPress\AiClient\Providers\DTO\ProviderMetadata $provider; /** * @var list<ModelMetadata> The available models. */ protected array $models; /** * Constructor. * * @since 0.1.0 * * @param ProviderMetadata $provider The provider metadata. * @param list<ModelMetadata> $models The available models. * * @throws InvalidArgumentException If models is not a list. */ public function __construct(\WordPress\AiClient\Providers\DTO\ProviderMetadata $provider, array $models) { if (!array_is_list($models)) { throw new InvalidArgumentException('Models must be a list array.'); } $this->provider = $provider; $this->models = $models; } /** * Creates a deep clone of this metadata. * * Clones the provider metadata and all model metadata objects * to ensure the cloned instance is independent of the original. * * @since 0.4.2 */ public function __clone() { // Clone provider metadata $this->provider = clone $this->provider; // Deep clone models array (ModelMetadata has __clone) $clonedModels = []; foreach ($this->models as $model) { $clonedModels[] = clone $model; } $this->models = $clonedModels; } /** * Gets the provider metadata. * * @since 0.1.0 * * @return ProviderMetadata The provider metadata. */ public function getProvider(): \WordPress\AiClient\Providers\DTO\ProviderMetadata { return $this->provider; } /** * Gets the available models. * * @since 0.1.0 * * @return list<ModelMetadata> The available models. */ public function getModels(): array { return $this->models; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_PROVIDER => \WordPress\AiClient\Providers\DTO\ProviderMetadata::getJsonSchema(), self::KEY_MODELS => ['type' => 'array', 'items' => ModelMetadata::getJsonSchema(), 'description' => 'The available models for this provider.']], 'required' => [self::KEY_PROVIDER, self::KEY_MODELS]]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return ProviderModelsMetadataArrayShape */ public function toArray(): array { return [self::KEY_PROVIDER => $this->provider->toArray(), self::KEY_MODELS => array_map(static fn(ModelMetadata $model): array => $model->toArray(), $this->models)]; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function fromArray(array $array): self { static::validateFromArrayData($array, [self::KEY_PROVIDER, self::KEY_MODELS]); return new self(\WordPress\AiClient\Providers\DTO\ProviderMetadata::fromArray($array[self::KEY_PROVIDER]), array_map(static fn(array $modelData): ModelMetadata => ModelMetadata::fromArray($modelData), $array[self::KEY_MODELS])); } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/DTO/ProviderMetadata.php������������������������������������������������������������������0000644�����������������00000017364�15250554011�0013124 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Providers\Enums\ProviderTypeEnum; use WordPress\AiClient\Providers\Http\Enums\RequestAuthenticationMethod; /** * Represents metadata about an AI provider. * * This class contains information about an AI provider, including its * unique identifier, display name, and type (cloud, server, or client). * * @since 0.1.0 * @since 1.2.0 Added optional description property. * @since 1.3.0 Added optional logoPath property. * * @phpstan-type ProviderMetadataArrayShape array{ * id: string, * name: string, * description?: ?string, * type: string, * credentialsUrl?: ?string, * authenticationMethod?: ?string, * logoPath?: ?string * } * * @extends AbstractDataTransferObject<ProviderMetadataArrayShape> */ class ProviderMetadata extends AbstractDataTransferObject { public const KEY_ID = 'id'; public const KEY_NAME = 'name'; public const KEY_DESCRIPTION = 'description'; public const KEY_TYPE = 'type'; public const KEY_CREDENTIALS_URL = 'credentialsUrl'; public const KEY_AUTHENTICATION_METHOD = 'authenticationMethod'; public const KEY_LOGO_PATH = 'logoPath'; /** * @var string The provider's unique identifier. */ protected string $id; /** * @var string The provider's display name. */ protected string $name; /** * @var string|null The provider's description. */ protected ?string $description; /** * @var ProviderTypeEnum The provider type. */ protected ProviderTypeEnum $type; /** * @var string|null The URL where users can get credentials. */ protected ?string $credentialsUrl; /** * @var RequestAuthenticationMethod|null The authentication method. */ protected ?RequestAuthenticationMethod $authenticationMethod; /** * @var string|null The full path to the provider's logo image file. */ protected ?string $logoPath; /** * Constructor. * * @since 0.1.0 * @since 1.2.0 Added optional $description parameter. * @since 1.3.0 Added optional $logoPath parameter. * * @param string $id The provider's unique identifier. * @param string $name The provider's display name. * @param ProviderTypeEnum $type The provider type. * @param string|null $credentialsUrl The URL where users can get credentials. * @param RequestAuthenticationMethod|null $authenticationMethod The authentication method. * @param string|null $description The provider's description. * @param string|null $logoPath The full path to the provider's logo image file. * @throws InvalidArgumentException If the provider ID contains invalid characters. */ public function __construct(string $id, string $name, ProviderTypeEnum $type, ?string $credentialsUrl = null, ?RequestAuthenticationMethod $authenticationMethod = null, ?string $description = null, ?string $logoPath = null) { if (!preg_match('/^[a-z0-9\-_]+$/', $id)) { throw new InvalidArgumentException(sprintf( // phpcs:ignore Generic.Files.LineLength.TooLong 'Invalid provider ID "%s". Only lowercase alphanumeric characters, hyphens, and underscores are allowed.', $id )); } $this->id = $id; $this->name = $name; $this->description = $description; $this->type = $type; $this->credentialsUrl = $credentialsUrl; $this->authenticationMethod = $authenticationMethod; $this->logoPath = $logoPath; } /** * Gets the provider's unique identifier. * * @since 0.1.0 * * @return string The provider ID. */ public function getId(): string { return $this->id; } /** * Gets the provider's display name. * * @since 0.1.0 * * @return string The provider name. */ public function getName(): string { return $this->name; } /** * Gets the provider's description. * * @since 1.2.0 * * @return string|null The provider description. */ public function getDescription(): ?string { return $this->description; } /** * Gets the provider type. * * @since 0.1.0 * * @return ProviderTypeEnum The provider type. */ public function getType(): ProviderTypeEnum { return $this->type; } /** * Gets the credentials URL. * * @since 0.1.0 * * @return string|null The credentials URL. */ public function getCredentialsUrl(): ?string { return $this->credentialsUrl; } /** * Gets the authentication method. * * @since 0.4.0 * * @return RequestAuthenticationMethod|null The authentication method. */ public function getAuthenticationMethod(): ?RequestAuthenticationMethod { return $this->authenticationMethod; } /** * Gets the full path to the provider's logo image file. * * @since 1.3.0 * * @return string|null The full path to the logo image file. */ public function getLogoPath(): ?string { return $this->logoPath; } /** * {@inheritDoc} * * @since 0.1.0 * @since 1.2.0 Added description to schema. * @since 1.3.0 Added logoPath to schema. */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_ID => ['type' => 'string', 'description' => 'The provider\'s unique identifier.'], self::KEY_NAME => ['type' => 'string', 'description' => 'The provider\'s display name.'], self::KEY_DESCRIPTION => ['type' => 'string', 'description' => 'The provider\'s description.'], self::KEY_TYPE => ['type' => 'string', 'enum' => ProviderTypeEnum::getValues(), 'description' => 'The provider type (cloud, server, or client).'], self::KEY_CREDENTIALS_URL => ['type' => 'string', 'description' => 'The URL where users can get credentials.'], self::KEY_AUTHENTICATION_METHOD => ['type' => ['string', 'null'], 'enum' => array_merge(RequestAuthenticationMethod::getValues(), [null]), 'description' => 'The authentication method.'], self::KEY_LOGO_PATH => ['type' => 'string', 'description' => 'The full path to the provider\'s logo image file.']], 'required' => [self::KEY_ID, self::KEY_NAME, self::KEY_TYPE]]; } /** * {@inheritDoc} * * @since 0.1.0 * @since 1.2.0 Added description to output. * @since 1.3.0 Added logoPath to output. * * @return ProviderMetadataArrayShape */ public function toArray(): array { return [self::KEY_ID => $this->id, self::KEY_NAME => $this->name, self::KEY_DESCRIPTION => $this->description, self::KEY_TYPE => $this->type->value, self::KEY_CREDENTIALS_URL => $this->credentialsUrl, self::KEY_AUTHENTICATION_METHOD => $this->authenticationMethod ? $this->authenticationMethod->value : null, self::KEY_LOGO_PATH => $this->logoPath]; } /** * {@inheritDoc} * * @since 0.1.0 * @since 1.2.0 Added description support. * @since 1.3.0 Added logoPath support. */ public static function fromArray(array $array): self { static::validateFromArrayData($array, [self::KEY_ID, self::KEY_NAME, self::KEY_TYPE]); return new self($array[self::KEY_ID], $array[self::KEY_NAME], ProviderTypeEnum::from($array[self::KEY_TYPE]), $array[self::KEY_CREDENTIALS_URL] ?? null, isset($array[self::KEY_AUTHENTICATION_METHOD]) ? RequestAuthenticationMethod::from($array[self::KEY_AUTHENTICATION_METHOD]) : null, $array[self::KEY_DESCRIPTION] ?? null, $array[self::KEY_LOGO_PATH] ?? null); } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Contracts/ModelMetadataDirectoryInterface.php���������������������������������������������0000644�����������������00000002347�15250554011�0017405 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Contracts; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Providers\Models\DTO\ModelMetadata; /** * Interface for accessing model metadata within a provider. * * Provides methods to list, check, and retrieve model metadata * for all models supported by a provider. * * @since 0.1.0 */ interface ModelMetadataDirectoryInterface { /** * Lists all available model metadata. * * @since 0.1.0 * * @return list<ModelMetadata> Array of model metadata. */ public function listModelMetadata(): array; /** * Checks if metadata exists for a specific model. * * @since 0.1.0 * * @param string $modelId Model identifier. * @return bool True if metadata exists, false otherwise. */ public function hasModelMetadata(string $modelId): bool; /** * Gets metadata for a specific model. * * @since 0.1.0 * * @param string $modelId Model identifier. * @return ModelMetadata Model metadata. * @throws InvalidArgumentException If model metadata not found. */ public function getModelMetadata(string $modelId): ModelMetadata; } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Contracts/ProviderWithOperationsHandlerInterface.php��������������������������������������0000644�����������������00000001235�15250554011�0021002 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Contracts; /** * Interface for providers that support operations handlers. * * Providers implementing this interface can return an operations handler * for managing long-running operations across all their models. * * @since 0.1.0 */ interface ProviderWithOperationsHandlerInterface { /** * Gets the operations handler for this provider. * * @since 0.1.0 * * @return ProviderOperationsHandlerInterface The operations handler. */ public static function operationsHandler(): \WordPress\AiClient\Providers\Contracts\ProviderOperationsHandlerInterface; } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Contracts/ProviderOperationsHandlerInterface.php������������������������������������������0000644�����������������00000001522�15250554011�0020145 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Contracts; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Operations\Contracts\OperationInterface; /** * Interface for handling provider-level operations. * * Provides methods to retrieve and manage long-running operations * across all models within a provider. Operations are tracked at the * provider level rather than per-model. * * @since 0.1.0 */ interface ProviderOperationsHandlerInterface { /** * Gets an operation by ID. * * @since 0.1.0 * * @param string $operationId Operation identifier. * @return OperationInterface The operation. * @throws InvalidArgumentException If operation not found. */ public function getOperation(string $operationId): OperationInterface; } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Contracts/ProviderInterface.php�����������������������������������������������������������0000644�����������������00000003322�15250554011�0014603 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Contracts; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Providers\DTO\ProviderMetadata; use WordPress\AiClient\Providers\Models\Contracts\ModelInterface; use WordPress\AiClient\Providers\Models\DTO\ModelConfig; /** * Interface for AI providers. * * Providers represent AI services (Google, OpenAI, Anthropic, etc.) * and provide access to models, metadata, and availability information. * * @since 0.1.0 */ interface ProviderInterface { /** * Gets provider metadata. * * @since 0.1.0 * * @return ProviderMetadata Provider metadata. */ public static function metadata(): ProviderMetadata; /** * Creates a model instance. * * @since 0.1.0 * * @param string $modelId Model identifier. * @param ?ModelConfig $modelConfig Model configuration. * @return ModelInterface Model instance. * @throws InvalidArgumentException If model not found or configuration invalid. */ public static function model(string $modelId, ?ModelConfig $modelConfig = null): ModelInterface; /** * Gets provider availability checker. * * @since 0.1.0 * * @return ProviderAvailabilityInterface Provider availability checker. */ public static function availability(): \WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface; /** * Gets model metadata directory. * * @since 0.1.0 * * @return ModelMetadataDirectoryInterface Model metadata directory. */ public static function modelMetadataDirectory(): \WordPress\AiClient\Providers\Contracts\ModelMetadataDirectoryInterface; } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Contracts/ProviderAvailabilityInterface.php�����������������������������������������������0000644�����������������00000001056�15250554011�0017140 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Contracts; /** * Interface for checking provider availability. * * Determines whether a provider is configured and available * for use based on API keys, credentials, or other requirements. * * @since 0.1.0 */ interface ProviderAvailabilityInterface { /** * Checks if the provider is configured. * * @since 0.1.0 * * @return bool True if the provider is configured and available, false otherwise. */ public function isConfigured(): bool; } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Http/Collections/HeadersCollection.php����������������������������������������������������0000644�����������������00000007411�15250554011�0016017 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\Collections; /** * Simple collection for managing HTTP headers with case-insensitive access. * * This class stores HTTP headers while preserving their original casing * and provides efficient case-insensitive lookups. * * @since 0.1.0 */ class HeadersCollection { /** * @var array<string, list<string>> The headers with original casing. */ private array $headers = []; /** * @var array<string, string> Map of lowercase header names to actual header names. */ private array $headersMap = []; /** * Constructor. * * @since 0.1.0 * * @param array<string, string|list<string>> $headers Initial headers. */ public function __construct(array $headers = []) { foreach ($headers as $name => $value) { $this->set($name, $value); } } /** * Gets a specific header value. * * @since 0.1.0 * * @param string $name The header name (case-insensitive). * @return list<string>|null The header value(s) or null if not found. */ public function get(string $name): ?array { $lowerName = strtolower($name); if (!isset($this->headersMap[$lowerName])) { return null; } $actualName = $this->headersMap[$lowerName]; return $this->headers[$actualName]; } /** * Gets all headers. * * @since 0.1.0 * * @return array<string, list<string>> All headers with their original casing. */ public function getAll(): array { return $this->headers; } /** * Gets header values as a comma-separated string. * * @since 0.1.0 * * @param string $name The header name (case-insensitive). * @return string|null The header values as a comma-separated string or null if not found. */ public function getAsString(string $name): ?string { $values = $this->get($name); return $values !== null ? implode(', ', $values) : null; } /** * Checks if a header exists. * * @since 0.1.0 * * @param string $name The header name (case-insensitive). * @return bool True if the header exists, false otherwise. */ public function has(string $name): bool { return isset($this->headersMap[strtolower($name)]); } /** * Sets a header value, replacing any existing value. * * @since 0.1.0 * * @param string $name The header name. * @param string|list<string> $value The header value(s). * @return void */ private function set(string $name, $value): void { if (is_array($value)) { $normalizedValues = array_values($value); } else { // Split comma-separated string into array $normalizedValues = array_map('trim', explode(',', $value)); } $lowerName = strtolower($name); // If header exists with different casing, remove the old casing if (isset($this->headersMap[$lowerName])) { $oldName = $this->headersMap[$lowerName]; if ($oldName !== $name) { unset($this->headers[$oldName]); } } // Always use the new casing $this->headers[$name] = $normalizedValues; $this->headersMap[$lowerName] = $name; } /** * Returns a new instance with the specified header. * * @since 0.1.0 * * @param string $name The header name. * @param string|list<string> $value The header value(s). * @return self A new instance with the header. */ public function withHeader(string $name, $value): self { $new = clone $this; $new->set($name, $value); return $new; } } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Http/DTO/Request.php����������������������������������������������������������������������0000644�����������������00000030047�15250554011�0012231 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\DTO; use JsonException; use WordPress\AiClientDependencies\Psr\Http\Message\RequestInterface; use WordPress\AiClient\Common\AbstractDataTransferObject; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Providers\Http\Collections\HeadersCollection; use WordPress\AiClient\Providers\Http\Enums\HttpMethodEnum; /** * Represents an HTTP request. * * This class encapsulates HTTP request data that can be converted * to PSR-7 requests by the HTTP transporter. * * @since 0.1.0 * * @phpstan-import-type RequestOptionsArrayShape from RequestOptions * @phpstan-type RequestArrayShape array{ * method: string, * uri: string, * headers: array<string, list<string>>, * body?: string|null, * options?: RequestOptionsArrayShape * } * * @extends AbstractDataTransferObject<RequestArrayShape> */ class Request extends AbstractDataTransferObject { public const KEY_METHOD = 'method'; public const KEY_URI = 'uri'; public const KEY_HEADERS = 'headers'; public const KEY_BODY = 'body'; public const KEY_OPTIONS = 'options'; /** * @var HttpMethodEnum The HTTP method. */ protected HttpMethodEnum $method; /** * @var string The request URI. */ protected string $uri; /** * @var HeadersCollection The request headers. */ protected HeadersCollection $headers; /** * @var array<string, mixed>|null The request data (for query params or form data). */ protected ?array $data = null; /** * @var string|null The request body (raw string content). */ protected ?string $body = null; /** * @var RequestOptions|null Request transport options. */ protected ?\WordPress\AiClient\Providers\Http\DTO\RequestOptions $options = null; /** * Constructor. * * @since 0.1.0 * * @param HttpMethodEnum $method The HTTP method. * @param string $uri The request URI. * @param array<string, string|list<string>> $headers The request headers. * @param string|array<string, mixed>|null $data The request data. * @param RequestOptions|null $options The request transport options. * * @throws InvalidArgumentException If the URI is empty. */ public function __construct(HttpMethodEnum $method, string $uri, array $headers = [], $data = null, ?\WordPress\AiClient\Providers\Http\DTO\RequestOptions $options = null) { if (empty($uri)) { throw new InvalidArgumentException('URI cannot be empty.'); } $this->method = $method; $this->uri = $uri; $this->headers = new HeadersCollection($headers); // Separate data and body based on type if (is_string($data)) { $this->body = $data; } elseif (is_array($data)) { $this->data = $data; } $this->options = $options; } /** * Creates a deep clone of this request. * * Clones the headers collection and request options to ensure * the cloned request is independent of the original. * The HTTP method enum is immutable and can be safely shared. * * @since 0.4.2 */ public function __clone() { // Clone headers collection $this->headers = clone $this->headers; // Clone request options if present (contains only primitives) if ($this->options !== null) { $this->options = clone $this->options; } // Note: $method is an immutable enum and can be safely shared } /** * Gets the HTTP method. * * @since 0.1.0 * * @return HttpMethodEnum The HTTP method. */ public function getMethod(): HttpMethodEnum { return $this->method; } /** * Gets the request URI. * * For GET requests with array data, appends the data as query parameters. * * @since 0.1.0 * * @return string The URI. */ public function getUri(): string { // If GET request with data, append as query parameters if ($this->method === HttpMethodEnum::GET() && $this->data !== null && !empty($this->data)) { $separator = str_contains($this->uri, '?') ? '&' : '?'; return $this->uri . $separator . http_build_query($this->data); } return $this->uri; } /** * Gets the request headers. * * @since 0.1.0 * * @return array<string, list<string>> The headers. */ public function getHeaders(): array { return $this->headers->getAll(); } /** * Gets a specific header value. * * @since 0.1.0 * * @param string $name The header name (case-insensitive). * @return list<string>|null The header value(s) or null if not found. */ public function getHeader(string $name): ?array { return $this->headers->get($name); } /** * Gets header values as a comma-separated string. * * @since 0.1.0 * * @param string $name The header name (case-insensitive). * @return string|null The header values as a comma-separated string, or null if not found. */ public function getHeaderAsString(string $name): ?string { return $this->headers->getAsString($name); } /** * Checks if a header exists. * * @since 0.1.0 * * @param string $name The header name (case-insensitive). * @return bool True if the header exists, false otherwise. */ public function hasHeader(string $name): bool { return $this->headers->has($name); } /** * Gets the request body. * * For GET requests, returns null. * For POST/PUT/PATCH requests: * - If body is set, returns it as-is * - If data is set and Content-Type is JSON, returns JSON-encoded data * - If data is set and Content-Type is form, returns URL-encoded data * * @since 0.1.0 * * @return string|null The body. * @throws JsonException If the data cannot be encoded to JSON. */ public function getBody(): ?string { // GET requests don't have a body if (!$this->method->hasBody()) { return null; } // If body is set, return it as-is if ($this->body !== null) { return $this->body; } // If data is set, encode based on content type if ($this->data !== null) { $contentType = $this->getContentType(); // JSON encoding if ($contentType !== null && stripos($contentType, 'application/json') !== \false) { return json_encode($this->data, \JSON_THROW_ON_ERROR); } // Default to URL encoding for forms return http_build_query($this->data); } return null; } /** * Gets the Content-Type header value. * * @since 0.1.0 * * @return string|null The Content-Type header value or null if not set. */ private function getContentType(): ?string { $values = $this->getHeader('Content-Type'); return $values !== null ? $values[0] : null; } /** * Returns a new instance with the specified header. * * @since 0.1.0 * * @param string $name The header name. * @param string|list<string> $value The header value(s). * @return self A new instance with the header. */ public function withHeader(string $name, $value): self { $newHeaders = $this->headers->withHeader($name, $value); $new = clone $this; $new->headers = $newHeaders; return $new; } /** * Returns a new instance with the specified data. * * @since 0.1.0 * * @param string|array<string, mixed> $data The request data. * @return self A new instance with the data. */ public function withData($data): self { $new = clone $this; if (is_string($data)) { $new->body = $data; $new->data = null; } elseif (is_array($data)) { $new->data = $data; $new->body = null; } else { $new->data = null; $new->body = null; } return $new; } /** * Gets the request data array. * * @since 0.1.0 * * @return array<string, mixed>|null The request data array. */ public function getData(): ?array { return $this->data; } /** * Gets the request options. * * @since 0.2.0 * * @return RequestOptions|null Request transport options when configured. */ public function getOptions(): ?\WordPress\AiClient\Providers\Http\DTO\RequestOptions { return $this->options; } /** * Returns a new instance with the specified request options. * * @since 0.2.0 * * @param RequestOptions|null $options The request options to apply. * @return self A new instance with the options. */ public function withOptions(?\WordPress\AiClient\Providers\Http\DTO\RequestOptions $options): self { $new = clone $this; $new->options = $options; return $new; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_METHOD => ['type' => 'string', 'description' => 'The HTTP method.'], self::KEY_URI => ['type' => 'string', 'description' => 'The request URI.'], self::KEY_HEADERS => ['type' => 'object', 'additionalProperties' => ['type' => 'array', 'items' => ['type' => 'string']], 'description' => 'The request headers.'], self::KEY_BODY => ['type' => ['string'], 'description' => 'The request body.'], self::KEY_OPTIONS => \WordPress\AiClient\Providers\Http\DTO\RequestOptions::getJsonSchema()], 'required' => [self::KEY_METHOD, self::KEY_URI, self::KEY_HEADERS]]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return RequestArrayShape */ public function toArray(): array { $array = [ self::KEY_METHOD => $this->method->value, self::KEY_URI => $this->getUri(), // Include query params if GET with data self::KEY_HEADERS => $this->headers->getAll(), ]; // Include body if present (getBody() handles the conversion) $body = $this->getBody(); if ($body !== null) { $array[self::KEY_BODY] = $body; } if ($this->options !== null) { $optionsArray = $this->options->toArray(); if (!empty($optionsArray)) { $array[self::KEY_OPTIONS] = $optionsArray; } } return $array; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function fromArray(array $array): self { static::validateFromArrayData($array, [self::KEY_METHOD, self::KEY_URI, self::KEY_HEADERS]); return new self(HttpMethodEnum::from($array[self::KEY_METHOD]), $array[self::KEY_URI], $array[self::KEY_HEADERS] ?? [], $array[self::KEY_BODY] ?? null, isset($array[self::KEY_OPTIONS]) ? \WordPress\AiClient\Providers\Http\DTO\RequestOptions::fromArray($array[self::KEY_OPTIONS]) : null); } /** * Creates a Request instance from a PSR-7 RequestInterface. * * @since 0.2.0 * * @param RequestInterface $psrRequest The PSR-7 request to convert. * @return self A new Request instance. * @throws InvalidArgumentException If the HTTP method is not supported. */ public static function fromPsrRequest(RequestInterface $psrRequest): self { $method = HttpMethodEnum::from($psrRequest->getMethod()); $uri = (string) $psrRequest->getUri(); // Convert PSR-7 headers to array format expected by our constructor /** @var array<string, list<string>> $headers */ $headers = $psrRequest->getHeaders(); // Get body content $body = $psrRequest->getBody()->getContents(); $bodyOrData = !empty($body) ? $body : null; return new self($method, $uri, $headers, $bodyOrData); } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Http/DTO/RequestOptions.php���������������������������������������������������������������0000644�����������������00000014523�15250554011�0013606 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; use WordPress\AiClient\Common\Exception\InvalidArgumentException; /** * Represents optional HTTP transport configuration for a single request. * * Provides mutable setters for working with timeouts and redirect handling. * * @since 0.2.0 * * @phpstan-type RequestOptionsArrayShape array{ * timeout?: float|null, * connectTimeout?: float|null, * maxRedirects?: int|null * } * * @extends AbstractDataTransferObject<RequestOptionsArrayShape> */ class RequestOptions extends AbstractDataTransferObject { public const KEY_TIMEOUT = 'timeout'; public const KEY_CONNECT_TIMEOUT = 'connectTimeout'; public const KEY_MAX_REDIRECTS = 'maxRedirects'; /** * @var float|null Maximum duration in seconds to wait for the full response. */ protected ?float $timeout = null; /** * @var float|null Maximum duration in seconds to wait for the initial connection. */ protected ?float $connectTimeout = null; /** * @var int|null Maximum number of redirects to follow. 0 disables redirects, null is unspecified. */ protected ?int $maxRedirects = null; /** * Sets the request timeout in seconds. * * @since 0.2.0 * * @param float|null $timeout Timeout in seconds. * @return void * * @throws InvalidArgumentException When timeout is negative. */ public function setTimeout(?float $timeout): void { $this->validateTimeout($timeout, self::KEY_TIMEOUT); $this->timeout = $timeout; } /** * Sets the connection timeout in seconds. * * @since 0.2.0 * * @param float|null $timeout Connection timeout in seconds. * @return void * * @throws InvalidArgumentException When timeout is negative. */ public function setConnectTimeout(?float $timeout): void { $this->validateTimeout($timeout, self::KEY_CONNECT_TIMEOUT); $this->connectTimeout = $timeout; } /** * Sets the maximum number of redirects to follow. * * Set to 0 to disable redirects, null for unspecified, or a positive integer * to enable redirects with a maximum count. * * @since 0.2.0 * * @param int|null $maxRedirects Maximum redirects to follow, or 0 to disable, or null for unspecified. * @return void * * @throws InvalidArgumentException When redirect count is negative. */ public function setMaxRedirects(?int $maxRedirects): void { if ($maxRedirects !== null && $maxRedirects < 0) { throw new InvalidArgumentException('Request option "maxRedirects" must be greater than or equal to 0.'); } $this->maxRedirects = $maxRedirects; } /** * Gets the request timeout in seconds. * * @since 0.2.0 * * @return float|null Timeout in seconds. */ public function getTimeout(): ?float { return $this->timeout; } /** * Gets the connection timeout in seconds. * * @since 0.2.0 * * @return float|null Connection timeout in seconds. */ public function getConnectTimeout(): ?float { return $this->connectTimeout; } /** * Checks whether redirects are allowed. * * @since 0.2.0 * * @return bool|null True when redirects are allowed (maxRedirects > 0), * false when disabled (maxRedirects = 0), * null when unspecified (maxRedirects = null). */ public function allowsRedirects(): ?bool { if ($this->maxRedirects === null) { return null; } return $this->maxRedirects > 0; } /** * Gets the maximum number of redirects to follow. * * @since 0.2.0 * * @return int|null Maximum redirects or null when not specified. */ public function getMaxRedirects(): ?int { return $this->maxRedirects; } /** * {@inheritDoc} * * @since 0.2.0 * * @return RequestOptionsArrayShape */ public function toArray(): array { $data = []; if ($this->timeout !== null) { $data[self::KEY_TIMEOUT] = $this->timeout; } if ($this->connectTimeout !== null) { $data[self::KEY_CONNECT_TIMEOUT] = $this->connectTimeout; } if ($this->maxRedirects !== null) { $data[self::KEY_MAX_REDIRECTS] = $this->maxRedirects; } return $data; } /** * {@inheritDoc} * * @since 0.2.0 */ public static function fromArray(array $array): self { $instance = new self(); if (isset($array[self::KEY_TIMEOUT])) { $instance->setTimeout((float) $array[self::KEY_TIMEOUT]); } if (isset($array[self::KEY_CONNECT_TIMEOUT])) { $instance->setConnectTimeout((float) $array[self::KEY_CONNECT_TIMEOUT]); } if (isset($array[self::KEY_MAX_REDIRECTS])) { $instance->setMaxRedirects((int) $array[self::KEY_MAX_REDIRECTS]); } return $instance; } /** * {@inheritDoc} * * @since 0.2.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_TIMEOUT => ['type' => ['number', 'null'], 'minimum' => 0, 'description' => 'Maximum duration in seconds to wait for the full response.'], self::KEY_CONNECT_TIMEOUT => ['type' => ['number', 'null'], 'minimum' => 0, 'description' => 'Maximum duration in seconds to wait for the initial connection.'], self::KEY_MAX_REDIRECTS => ['type' => ['integer', 'null'], 'minimum' => 0, 'description' => 'Maximum redirects to follow. 0 disables, null is unspecified.']], 'additionalProperties' => \false]; } /** * Validates timeout values. * * @since 0.2.0 * * @param float|null $value Timeout to validate. * @param string $fieldName Field name for the error message. * * @throws InvalidArgumentException When timeout is negative. */ private function validateTimeout(?float $value, string $fieldName): void { if ($value !== null && $value < 0) { throw new InvalidArgumentException(sprintf('Request option "%s" must be greater than or equal to 0.', $fieldName)); } } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Http/DTO/error_log������������������������������������������������������������������������0000644�����������������00000042772�15250554011�0012015 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������[15-Jul-2026 16:16:57 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php:31 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php on line 31 [15-Jul-2026 16:16:57 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php on line 25 [15-Jul-2026 16:16:57 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php on line 19 [15-Jul-2026 16:16:57 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php on line 23 [24-Jul-2026 12:15:24 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php on line 19 [24-Jul-2026 12:15:24 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php on line 23 [24-Jul-2026 12:15:24 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php:31 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php on line 31 [24-Jul-2026 12:15:24 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php on line 25 [25-Jul-2026 05:33:27 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php on line 25 [25-Jul-2026 05:33:28 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php on line 23 [25-Jul-2026 05:33:28 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php:31 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php on line 31 [25-Jul-2026 05:33:28 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php on line 19 [04-Aug-2026 23:27:36 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php on line 23 [04-Aug-2026 23:27:36 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php on line 19 [04-Aug-2026 23:27:37 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php:31 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php on line 31 [04-Aug-2026 23:27:37 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php on line 25 [13-Aug-2026 01:33:38 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php on line 25 [13-Aug-2026 01:33:38 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php:31 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php on line 31 [13-Aug-2026 01:33:38 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php on line 19 [13-Aug-2026 01:33:39 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php on line 23 [13-Aug-2026 12:59:53 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php on line 23 [13-Aug-2026 12:59:53 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php on line 19 [13-Aug-2026 12:59:53 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php:31 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php on line 31 [13-Aug-2026 12:59:54 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php on line 25 [19-Aug-2026 21:32:06 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php:31 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php on line 31 [19-Aug-2026 21:32:06 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php on line 25 [19-Aug-2026 21:32:06 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php on line 19 [19-Aug-2026 21:32:06 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php on line 23 [19-Aug-2026 22:05:51 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php on line 19 [19-Aug-2026 22:05:51 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php on line 23 [19-Aug-2026 22:05:51 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php on line 25 [19-Aug-2026 22:05:52 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php:31 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php on line 31 [19-Aug-2026 23:24:02 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php on line 23 [19-Aug-2026 23:24:02 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php:31 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php on line 31 [19-Aug-2026 23:24:02 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php on line 25 [19-Aug-2026 23:24:02 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php on line 19 [03-Sep-2026 06:55:29 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php on line 25 [03-Sep-2026 06:55:36 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php:31 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php on line 31 [03-Sep-2026 23:30:31 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php on line 19 [03-Sep-2026 23:30:32 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php on line 23 [04-Sep-2026 06:37:14 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php:31 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php on line 31 [04-Sep-2026 06:37:16 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php on line 25 [05-Sep-2026 07:01:35 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php on line 19 [05-Sep-2026 07:01:46 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php on line 23 [05-Sep-2026 08:00:45 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php:31 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php on line 31 [05-Sep-2026 08:00:45 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php on line 25 ������Providers/Http/DTO/Response.php���������������������������������������������������������������������0000644�����������������00000013734�15250554011�0012403 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Providers\Http\Collections\HeadersCollection; /** * Represents an HTTP response. * * This class encapsulates HTTP response data that has been converted * from PSR-7 responses by the HTTP transporter. * * @since 0.1.0 * * @phpstan-type ResponseArrayShape array{ * statusCode: int, * headers: array<string, list<string>>, * body?: string|null * } * * @extends AbstractDataTransferObject<ResponseArrayShape> */ class Response extends AbstractDataTransferObject { public const KEY_STATUS_CODE = 'statusCode'; public const KEY_HEADERS = 'headers'; public const KEY_BODY = 'body'; /** * @var int The HTTP status code. */ protected int $statusCode; /** * @var HeadersCollection The response headers. */ protected HeadersCollection $headers; /** * @var string|null The response body. */ protected ?string $body; /** * Constructor. * * @since 0.1.0 * * @param int $statusCode The HTTP status code. * @param array<string, string|list<string>> $headers The response headers. * @param string|null $body The response body. * * @throws InvalidArgumentException If the status code is invalid. */ public function __construct(int $statusCode, array $headers, ?string $body = null) { if ($statusCode < 100 || $statusCode >= 600) { throw new InvalidArgumentException('Invalid HTTP status code: ' . $statusCode); } $this->statusCode = $statusCode; $this->headers = new HeadersCollection($headers); $this->body = $body; } /** * Creates a deep clone of this response. * * Clones the headers collection to ensure the cloned * response is independent of the original. * * @since 0.4.2 */ public function __clone() { // Clone headers collection $this->headers = clone $this->headers; } /** * Gets the HTTP status code. * * @since 0.1.0 * * @return int The status code. */ public function getStatusCode(): int { return $this->statusCode; } /** * Gets the response headers. * * @since 0.1.0 * * @return array<string, list<string>> The headers. */ public function getHeaders(): array { return $this->headers->getAll(); } /** * Gets a specific header value. * * @since 0.1.0 * * @param string $name The header name (case-insensitive). * @return list<string>|null The header value(s) or null if not found. */ public function getHeader(string $name): ?array { return $this->headers->get($name); } /** * Gets header values as a comma-separated string. * * @since 0.1.0 * * @param string $name The header name (case-insensitive). * @return string|null The header values as a comma-separated string or null if not found. */ public function getHeaderAsString(string $name): ?string { return $this->headers->getAsString($name); } /** * Gets the response body. * * @since 0.1.0 * * @return string|null The body. */ public function getBody(): ?string { return $this->body; } /** * Checks if the response has a header. * * @since 0.1.0 * * @param string $name The header name. * @return bool True if the header exists, false otherwise. */ public function hasHeader(string $name): bool { return $this->headers->has($name); } /** * Checks if the response indicates success. * * @since 0.1.0 * * @return bool True if status code is 2xx, false otherwise. */ public function isSuccessful(): bool { return $this->statusCode >= 200 && $this->statusCode < 300; } /** * Gets the response data as an array. * * Attempts to decode the body as JSON. Returns null if the body * is empty or not valid JSON. * * @since 0.1.0 * * @return array<string, mixed>|null The decoded data or null. */ public function getData(): ?array { if ($this->body === null || $this->body === '') { return null; } $data = json_decode($this->body, \true); if (json_last_error() !== \JSON_ERROR_NONE) { return null; } /** @var array<string, mixed>|null $data */ return is_array($data) ? $data : null; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_STATUS_CODE => ['type' => 'integer', 'minimum' => 100, 'maximum' => 599, 'description' => 'The HTTP status code.'], self::KEY_HEADERS => ['type' => 'object', 'additionalProperties' => ['type' => 'array', 'items' => ['type' => 'string']], 'description' => 'The response headers.'], self::KEY_BODY => ['type' => ['string', 'null'], 'description' => 'The response body.']], 'required' => [self::KEY_STATUS_CODE, self::KEY_HEADERS]]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return ResponseArrayShape */ public function toArray(): array { $data = [self::KEY_STATUS_CODE => $this->statusCode, self::KEY_HEADERS => $this->headers->getAll()]; if ($this->body !== null) { $data[self::KEY_BODY] = $this->body; } return $data; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function fromArray(array $array): self { static::validateFromArrayData($array, [self::KEY_STATUS_CODE, self::KEY_HEADERS]); return new self($array[self::KEY_STATUS_CODE], $array[self::KEY_HEADERS], $array[self::KEY_BODY] ?? null); } } ������������������������������������Providers/Http/DTO/ApiKeyRequestAuthentication.php��������������������������������������������������0000644�����������������00000004517�15250554011�0016237 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; use WordPress\AiClient\Providers\Http\Contracts\RequestAuthenticationInterface; /** * Class for HTTP request authentication using an API key. * * @since 0.1.0 * * @phpstan-type ApiKeyRequestAuthenticationArrayShape array{ * apiKey: string * } * * @extends AbstractDataTransferObject<ApiKeyRequestAuthenticationArrayShape> */ class ApiKeyRequestAuthentication extends AbstractDataTransferObject implements RequestAuthenticationInterface { public const KEY_API_KEY = 'apiKey'; /** * @var string The API key used for authentication. */ protected string $apiKey; /** * Constructor. * * @since 0.1.0 * * @param string $apiKey The API key used for authentication. */ public function __construct(string $apiKey) { $this->apiKey = $apiKey; } /** * {@inheritDoc} * * @since 0.1.0 */ public function authenticateRequest(\WordPress\AiClient\Providers\Http\DTO\Request $request): \WordPress\AiClient\Providers\Http\DTO\Request { // Add the API key to the request headers. return $request->withHeader('Authorization', 'Bearer ' . $this->apiKey); } /** * Gets the API key. * * @since 0.1.0 * * @return string The API key. */ public function getApiKey(): string { return $this->apiKey; } /** * {@inheritDoc} * * @since 0.1.0 * * @since 0.1.0 * * @return ApiKeyRequestAuthenticationArrayShape */ public function toArray(): array { return [self::KEY_API_KEY => $this->apiKey]; } /** * {@inheritDoc} * * @since 0.1.0 * * @since 0.1.0 */ public static function fromArray(array $array): self { static::validateFromArrayData($array, [self::KEY_API_KEY]); return new self($array[self::KEY_API_KEY]); } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_API_KEY => ['type' => 'string', 'title' => 'API Key', 'description' => 'The API key used for authentication.']], 'required' => [self::KEY_API_KEY]]; } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Http/Contracts/ClientWithOptionsInterface.php���������������������������������������������0000644�����������������00000002001�15250554011�0017347 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\Contracts; use WordPress\AiClientDependencies\Psr\Http\Message\RequestInterface; use WordPress\AiClientDependencies\Psr\Http\Message\ResponseInterface; use WordPress\AiClient\Providers\Http\DTO\RequestOptions; /** * Interface for HTTP clients that support per-request transport options. * * Extends the capabilities of PSR-18 clients by allowing custom transport * configuration such as timeouts and redirect handling on each request. * * @since 0.2.0 */ interface ClientWithOptionsInterface { /** * Sends an HTTP request with the given transport options. * * @since 0.2.0 * * @param RequestInterface $request The PSR-7 request to send. * @param RequestOptions $options The request transport options. Must not be null. * @return ResponseInterface The PSR-7 response received. */ public function sendRequestWithOptions(RequestInterface $request, RequestOptions $options): ResponseInterface; } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Http/Contracts/error_log������������������������������������������������������������������0000644�����������������00000011437�15250554011�0013321 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������[15-Jul-2026 16:16:59 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithJsonSchemaInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php:13 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php on line 13 [24-Jul-2026 12:15:28 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithJsonSchemaInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php:13 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php on line 13 [25-Jul-2026 05:33:25 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithJsonSchemaInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php:13 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php on line 13 [04-Aug-2026 23:27:32 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithJsonSchemaInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php:13 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php on line 13 [13-Aug-2026 01:33:32 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithJsonSchemaInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php:13 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php on line 13 [13-Aug-2026 12:59:55 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithJsonSchemaInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php:13 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php on line 13 [19-Aug-2026 21:32:05 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithJsonSchemaInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php:13 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php on line 13 [19-Aug-2026 22:05:58 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithJsonSchemaInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php:13 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php on line 13 [19-Aug-2026 23:23:53 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithJsonSchemaInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php:13 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php on line 13 [03-Sep-2026 23:32:50 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithJsonSchemaInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php:13 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php on line 13 [05-Sep-2026 07:01:41 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithJsonSchemaInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php:13 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php on line 13 ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Http/Contracts/RequestAuthenticationInterface.php�����������������������������������������0000644�����������������00000001155�15250554011�0020262 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\Contracts; use WordPress\AiClient\Common\Contracts\WithJsonSchemaInterface; use WordPress\AiClient\Providers\Http\DTO\Request; /** * Interface for HTTP request authentication. * * @since 0.1.0 */ interface RequestAuthenticationInterface extends WithJsonSchemaInterface { /** * Authenticates an HTTP request. * * @since 0.1.0 * * @param Request $request The request to authenticate. * @return Request The authenticated request. */ public function authenticateRequest(Request $request): Request; } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Http/Contracts/WithHttpTransporterInterface.php�������������������������������������������0000644�����������������00000001455�15250554011�0017754 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\Contracts; /** * Interface for models that require HTTP transport capabilities. * * @since 0.1.0 */ interface WithHttpTransporterInterface { /** * Sets the HTTP transporter. * * @since 0.1.0 * * @param HttpTransporterInterface $transporter The HTTP transporter instance. * @return void */ public function setHttpTransporter(\WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface $transporter): void; /** * Returns the HTTP transporter. * * @since 0.1.0 * * @return HttpTransporterInterface The HTTP transporter instance. */ public function getHttpTransporter(): \WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface; } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Http/Contracts/HttpTransporterInterface.php�����������������������������������������������0000644�����������������00000001534�15250554011�0017116 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\Contracts; use WordPress\AiClient\Providers\Http\DTO\Request; use WordPress\AiClient\Providers\Http\DTO\RequestOptions; use WordPress\AiClient\Providers\Http\DTO\Response; /** * Interface for HTTP transport implementations. * * Handles sending HTTP requests and receiving responses using * PSR-7, PSR-17, and PSR-18 standards internally. * * @since 0.1.0 */ interface HttpTransporterInterface { /** * Sends an HTTP request and returns the response. * * @since 0.1.0 * * @param Request $request The request to send. * @param RequestOptions|null $options Optional transport options for the request. * @return Response The response received. */ public function send(Request $request, ?RequestOptions $options = null): Response; } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Http/Contracts/WithRequestAuthenticationInterface.php�������������������������������������0000644�����������������00000001540�15250554011�0021114 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\Contracts; /** * Interface for models that support request authentication. * * @since 0.1.0 */ interface WithRequestAuthenticationInterface { /** * Sets the request authentication. * * @since 0.1.0 * * @param RequestAuthenticationInterface $authentication The authentication instance. * @return void */ public function setRequestAuthentication(\WordPress\AiClient\Providers\Http\Contracts\RequestAuthenticationInterface $authentication): void; /** * Returns the request authentication. * * @since 0.1.0 * * @return RequestAuthenticationInterface The authentication instance. */ public function getRequestAuthentication(): \WordPress\AiClient\Providers\Http\Contracts\RequestAuthenticationInterface; } ����������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Http/Util/ErrorMessageExtractor.php�������������������������������������������������������0000644�����������������00000003545�15250554011�0015365 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\Util; /** * Utility for extracting error messages from API response data. * * Centralizes the logic for parsing common API error response formats * to avoid code duplication across exception classes. * * @since 0.2.0 * @since 0.4.0 Moved from Utilities namespace to Util namespace. */ class ErrorMessageExtractor { /** * Extracts error message from API response data. * * Handles common error response formats: * - { "error": { "message": "Error text" } } * - { "error": "Error text" } * - { "message": "Error text" } * * @since 0.2.0 * * @param mixed $data The response data to extract error message from. * @return string|null The extracted error message, or null if none found. */ public static function extractFromResponseData($data): ?string { if (!is_array($data)) { return null; } // Handle [ { "error": { "message": "Error text" } } ] if (isset($data[0]) && is_array($data[0]) && isset($data[0]['error']) && is_array($data[0]['error']) && isset($data[0]['error']['message']) && is_string($data[0]['error']['message'])) { return $data[0]['error']['message']; } // Handle { "error": { "message": "Error text" } } if (isset($data['error']) && is_array($data['error']) && isset($data['error']['message']) && is_string($data['error']['message'])) { return $data['error']['message']; } // Handle { "error": "Error text" } if (isset($data['error']) && is_string($data['error'])) { return $data['error']; } // Handle { "message": "Error text" } if (isset($data['message']) && is_string($data['message'])) { return $data['message']; } return null; } } �����������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Http/Util/ResponseUtil.php����������������������������������������������������������������0000644�����������������00000004146�15250554011�0013525 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\Util; use WordPress\AiClient\Providers\Http\DTO\Response; use WordPress\AiClient\Providers\Http\Exception\ClientException; use WordPress\AiClient\Providers\Http\Exception\RedirectException; use WordPress\AiClient\Providers\Http\Exception\ServerException; /** * Class with static utility methods to process HTTP responses. * * @since 0.1.0 */ class ResponseUtil { /** * Throws an appropriate exception if the given response is not successful. * * This method checks the HTTP status code of the response and throws * the appropriate exception type based on the status code range: * - 3xx: RedirectException (redirect responses) * - 4xx: ClientException (client errors) * - 5xx: ServerException (server errors) * - Other unsuccessful responses: RuntimeException (invalid status codes) * * @since 0.1.0 * * @param Response $response The HTTP response to check. * @throws RedirectException If the response indicates a redirect (3xx). * @throws ClientException If the response indicates a client error (4xx). * @throws ServerException If the response indicates a server error (5xx). * @throws \RuntimeException If the response has an invalid status code. */ public static function throwIfNotSuccessful(Response $response): void { if ($response->isSuccessful()) { return; } $statusCode = $response->getStatusCode(); // 3xx Redirect Responses if ($statusCode >= 300 && $statusCode < 400) { throw RedirectException::fromRedirectResponse($response); } // 4xx Client Errors if ($statusCode >= 400 && $statusCode < 500) { throw ClientException::fromClientErrorResponse($response); } // 5xx Server Errors if ($statusCode >= 500 && $statusCode < 600) { throw ServerException::fromServerErrorResponse($response); } throw new \RuntimeException(sprintf('Response returned invalid status code: %s', $response->getStatusCode())); } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Http/error_log����������������������������������������������������������������������������0000644�����������������00000011360�15250554011�0011354 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������[15-Jul-2026 16:16:54 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php:29 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php on line 29 [24-Jul-2026 12:15:23 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php:29 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php on line 29 [25-Jul-2026 05:33:22 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php:29 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php on line 29 [04-Aug-2026 23:27:30 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php:29 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php on line 29 [13-Aug-2026 01:33:28 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php:29 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php on line 29 [13-Aug-2026 12:59:45 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php:29 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php on line 29 [19-Aug-2026 21:32:05 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php:29 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php on line 29 [19-Aug-2026 22:05:47 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php:29 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php on line 29 [19-Aug-2026 23:23:39 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php:29 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php on line 29 [03-Sep-2026 06:52:18 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php:29 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php on line 29 [03-Sep-2026 18:00:57 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php:29 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php on line 29 [04-Sep-2026 22:38:24 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php:29 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php on line 29 ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Http/Enums/HttpMethodEnum.php�������������������������������������������������������������0000644�����������������00000004750�15250554011�0014151 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\Enums; use WordPress\AiClient\Common\AbstractEnum; /** * Represents HTTP request methods. * * @since 0.1.0 * * @method static self GET() * @method static self POST() * @method static self PUT() * @method static self PATCH() * @method static self DELETE() * @method static self HEAD() * @method static self OPTIONS() * @method static self CONNECT() * @method static self TRACE() * * @method bool isGet() * @method bool isPost() * @method bool isPut() * @method bool isPatch() * @method bool isDelete() * @method bool isHead() * @method bool isOptions() * @method bool isConnect() * @method bool isTrace() */ final class HttpMethodEnum extends AbstractEnum { /** * GET method for retrieving resources. * * @var string */ public const GET = 'GET'; /** * POST method for creating resources. * * @var string */ public const POST = 'POST'; /** * PUT method for updating/replacing resources. * * @var string */ public const PUT = 'PUT'; /** * PATCH method for partially updating resources. * * @var string */ public const PATCH = 'PATCH'; /** * DELETE method for removing resources. * * @var string */ public const DELETE = 'DELETE'; /** * HEAD method for retrieving headers only. * * @var string */ public const HEAD = 'HEAD'; /** * OPTIONS method for retrieving allowed methods. * * @var string */ public const OPTIONS = 'OPTIONS'; /** * CONNECT method for establishing tunnel. * * @var string */ public const CONNECT = 'CONNECT'; /** * TRACE method for diagnostic purposes. * * @var string */ public const TRACE = 'TRACE'; /** * Checks if this method is idempotent. * * @since 0.1.0 * * @return bool True if the method is idempotent, false otherwise. */ public function isIdempotent(): bool { return in_array($this->value, [self::GET, self::HEAD, self::OPTIONS, self::TRACE, self::PUT, self::DELETE], \true); } /** * Checks if this method typically has a request body. * * @since 0.1.0 * * @return bool True if the method typically has a body, false otherwise. */ public function hasBody(): bool { return in_array($this->value, [self::POST, self::PUT, self::PATCH], \true); } } ������������������������Providers/Http/Enums/error_log����������������������������������������������������������������������0000644�����������������00000020706�15250554011�0012447 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������[15-Jul-2026 16:16:58 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php:32 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php on line 32 [15-Jul-2026 16:16:58 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php on line 18 [24-Jul-2026 12:15:25 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php:32 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php on line 32 [24-Jul-2026 12:15:25 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php on line 18 [25-Jul-2026 05:33:32 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php on line 18 [25-Jul-2026 05:33:33 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php:32 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php on line 32 [04-Aug-2026 23:27:40 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php:32 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php on line 32 [04-Aug-2026 23:27:40 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php on line 18 [13-Aug-2026 01:33:29 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php:32 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php on line 32 [13-Aug-2026 01:33:29 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php on line 18 [13-Aug-2026 12:59:46 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php:32 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php on line 32 [13-Aug-2026 12:59:47 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php on line 18 [19-Aug-2026 21:32:06 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php:32 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php on line 32 [19-Aug-2026 21:32:06 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php on line 18 [19-Aug-2026 22:05:54 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php on line 18 [19-Aug-2026 22:05:55 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php:32 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php on line 32 [19-Aug-2026 23:23:48 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php:32 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php on line 32 [19-Aug-2026 23:23:49 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php on line 18 [03-Sep-2026 23:32:35 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php on line 18 [03-Sep-2026 23:33:00 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php:32 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php on line 32 [05-Sep-2026 07:01:35 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php on line 18 [05-Sep-2026 07:01:56 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php:32 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php on line 32 ����������������������������������������������������������Providers/Http/Enums/RequestAuthenticationMethod.php������������������������������������������������0000644�����������������00000002344�15250554011�0016732 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\Enums; use WordPress\AiClient\Common\AbstractEnum; use WordPress\AiClient\Common\Contracts\WithArrayTransformationInterface; use WordPress\AiClient\Providers\Http\Contracts\RequestAuthenticationInterface; use WordPress\AiClient\Providers\Http\DTO\ApiKeyRequestAuthentication; /** * Enum for request authentication methods. * * @since 0.4.0 * * @method static self apiKey() Creates an instance for API_KEY method. * @method bool isApiKey() Checks if the method is API_KEY. */ class RequestAuthenticationMethod extends AbstractEnum { /** * API key authentication. */ public const API_KEY = 'api_key'; /** * Gets the implementation class for the authentication method. * * @since 0.4.0 * * @return class-string<RequestAuthenticationInterface&WithArrayTransformationInterface> The implementation class. * * @phpstan-ignore missingType.generics */ public function getImplementationClass(): string { // At the moment, this is the only supported method. // Once more methods are available, add conditionals here for each method. return ApiKeyRequestAuthentication::class; } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php����������������������������������������0000644�����������������00000005557�15250554011�0020426 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\Abstracts; use WordPress\AiClientDependencies\Http\Discovery\Psr18ClientDiscovery; use WordPress\AiClientDependencies\Http\Discovery\Strategy\DiscoveryStrategy; use WordPress\AiClientDependencies\Nyholm\Psr7\Factory\Psr17Factory; use WordPress\AiClientDependencies\Psr\Http\Client\ClientInterface; /** * Abstract discovery strategy for HTTP client implementations. * * Provides a base for registering custom HTTP client implementations * with HTTPlug's discovery mechanism. Subclasses must implement * the createClient() method to provide their specific PSR-18 * HTTP client instance using the provided Psr17Factory. * * @since 1.1.0 */ abstract class AbstractClientDiscoveryStrategy implements DiscoveryStrategy { /** * Initializes and registers the discovery strategy. * * @since 1.1.0 * * @return void */ public static function init(): void { if (!class_exists('WordPress\AiClientDependencies\Http\Discovery\Psr18ClientDiscovery')) { return; } Psr18ClientDiscovery::prependStrategy(static::class); } /** * {@inheritDoc} * * @since 1.1.0 * * @param string $type The type of discovery. * @return array<array<string, mixed>> The discovery candidates. */ public static function getCandidates($type) { if (ClientInterface::class === $type) { return [['class' => static function () { $psr17Factory = new Psr17Factory(); return static::createClient($psr17Factory); }]]; } $psr17Factories = ['WordPress\AiClientDependencies\Psr\Http\Message\RequestFactoryInterface', 'WordPress\AiClientDependencies\Psr\Http\Message\ResponseFactoryInterface', 'WordPress\AiClientDependencies\Psr\Http\Message\ServerRequestFactoryInterface', 'WordPress\AiClientDependencies\Psr\Http\Message\StreamFactoryInterface', 'WordPress\AiClientDependencies\Psr\Http\Message\UploadedFileFactoryInterface', 'WordPress\AiClientDependencies\Psr\Http\Message\UriFactoryInterface']; if (in_array($type, $psr17Factories, \true)) { return [['class' => Psr17Factory::class]]; } return []; } /** * Creates an instance of the HTTP client. * * Subclasses must implement this method to return their specific * PSR-18 HTTP client instance. The provided Psr17Factory implements * all PSR-17 interfaces (RequestFactory, ResponseFactory, StreamFactory, * etc.) and can be used to satisfy client constructor dependencies. * * @since 1.1.0 * * @param Psr17Factory $psr17Factory The PSR-17 factory for creating HTTP messages. * @return ClientInterface The PSR-18 HTTP client. */ abstract protected static function createClient(Psr17Factory $psr17Factory): ClientInterface; } �������������������������������������������������������������������������������������������������������������������������������������������������Providers/Http/Abstracts/error_log������������������������������������������������������������������0000644�����������������00000011704�15250554011�0013304 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������[15-Jul-2026 16:17:01 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClientDependencies\Http\Discovery\Strategy\DiscoveryStrategy" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php on line 20 [24-Jul-2026 12:15:27 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClientDependencies\Http\Discovery\Strategy\DiscoveryStrategy" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php on line 20 [25-Jul-2026 05:33:27 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClientDependencies\Http\Discovery\Strategy\DiscoveryStrategy" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php on line 20 [04-Aug-2026 23:27:35 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClientDependencies\Http\Discovery\Strategy\DiscoveryStrategy" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php on line 20 [13-Aug-2026 01:33:31 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClientDependencies\Http\Discovery\Strategy\DiscoveryStrategy" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php on line 20 [13-Aug-2026 12:59:52 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClientDependencies\Http\Discovery\Strategy\DiscoveryStrategy" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php on line 20 [19-Aug-2026 21:32:06 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClientDependencies\Http\Discovery\Strategy\DiscoveryStrategy" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php on line 20 [19-Aug-2026 22:06:04 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClientDependencies\Http\Discovery\Strategy\DiscoveryStrategy" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php on line 20 [19-Aug-2026 23:24:00 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClientDependencies\Http\Discovery\Strategy\DiscoveryStrategy" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php on line 20 [03-Sep-2026 23:30:30 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClientDependencies\Http\Discovery\Strategy\DiscoveryStrategy" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php on line 20 [05-Sep-2026 07:01:34 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClientDependencies\Http\Discovery\Strategy\DiscoveryStrategy" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php on line 20 ������������������������������������������������������������Providers/Http/HttpTransporterFactory.php�����������������������������������������������������������0000644�����������������00000002026�15250554011�0014662 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http; use WordPress\AiClientDependencies\Http\Discovery\Psr17FactoryDiscovery; use WordPress\AiClientDependencies\Http\Discovery\Psr18ClientDiscovery; use WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface; /** * Factory for creating HTTP transporters. * * Uses HTTPlug's Discovery component to automatically find * available HTTP clients and factories. * * @since 0.1.0 */ class HttpTransporterFactory { /** * Creates an HTTP transporter. * * Uses HTTPlug Discovery to automatically find PSR-18 client * and PSR-17 factories if not provided. * * @since 0.1.0 * * @return HttpTransporterInterface The HTTP transporter. */ public static function createTransporter(): HttpTransporterInterface { return new \WordPress\AiClient\Providers\Http\HttpTransporter(Psr18ClientDiscovery::find(), Psr17FactoryDiscovery::findRequestFactory(), Psr17FactoryDiscovery::findStreamFactory()); } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Http/HttpTransporter.php������������������������������������������������������������������0000644�����������������00000025234�15250554011�0013340 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http; use WordPress\AiClientDependencies\Http\Discovery\Psr17FactoryDiscovery; use WordPress\AiClientDependencies\Http\Discovery\Psr18ClientDiscovery; use WordPress\AiClientDependencies\Psr\Http\Client\ClientInterface; use WordPress\AiClientDependencies\Psr\Http\Message\RequestFactoryInterface; use WordPress\AiClientDependencies\Psr\Http\Message\RequestInterface; use WordPress\AiClientDependencies\Psr\Http\Message\ResponseInterface; use WordPress\AiClientDependencies\Psr\Http\Message\StreamFactoryInterface; use WordPress\AiClient\Common\Exception\RuntimeException; use WordPress\AiClient\Providers\Http\Contracts\ClientWithOptionsInterface; use WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface; use WordPress\AiClient\Providers\Http\DTO\Request; use WordPress\AiClient\Providers\Http\DTO\RequestOptions; use WordPress\AiClient\Providers\Http\DTO\Response; use WordPress\AiClient\Providers\Http\Exception\NetworkException; /** * HTTP transporter implementation using HTTPlug. * * This class handles the conversion between custom Request/Response * objects and PSR-7 messages, using HTTPlug for client abstraction * and PSR-17 factories for message creation. * * @since 0.1.0 */ class HttpTransporter implements HttpTransporterInterface { /** * @var RequestFactoryInterface PSR-17 request factory. */ private RequestFactoryInterface $requestFactory; /** * @var StreamFactoryInterface PSR-17 stream factory. */ private StreamFactoryInterface $streamFactory; /** * @var ClientInterface PSR-18 HTTP client. */ private ClientInterface $client; /** * Constructor. * * @since 0.1.0 * * @param ClientInterface|null $client PSR-18 HTTP client. * @param RequestFactoryInterface|null $requestFactory PSR-17 request factory. * @param StreamFactoryInterface|null $streamFactory PSR-17 stream factory. */ public function __construct(?ClientInterface $client = null, ?RequestFactoryInterface $requestFactory = null, ?StreamFactoryInterface $streamFactory = null) { $this->client = $client ?: Psr18ClientDiscovery::find(); $this->requestFactory = $requestFactory ?: Psr17FactoryDiscovery::findRequestFactory(); $this->streamFactory = $streamFactory ?: Psr17FactoryDiscovery::findStreamFactory(); } /** * {@inheritDoc} * * @since 0.1.0 * @since 0.2.0 Added optional RequestOptions parameter and ClientWithOptions support. */ public function send(Request $request, ?RequestOptions $options = null): Response { $psr7Request = $this->convertToPsr7Request($request); // Merge request options with parameter options, with parameter options taking precedence $mergedOptions = $this->mergeOptions($request->getOptions(), $options); try { $hasOptions = $mergedOptions !== null; if ($hasOptions && $this->client instanceof ClientWithOptionsInterface) { $psr7Response = $this->client->sendRequestWithOptions($psr7Request, $mergedOptions); } elseif ($hasOptions && $this->isGuzzleClient($this->client)) { $psr7Response = $this->sendWithGuzzle($psr7Request, $mergedOptions); } else { $psr7Response = $this->client->sendRequest($psr7Request); } } catch (\WordPress\AiClientDependencies\Psr\Http\Client\NetworkExceptionInterface $e) { throw NetworkException::fromPsr18NetworkException($psr7Request, $e); } catch (\WordPress\AiClientDependencies\Psr\Http\Client\ClientExceptionInterface $e) { // Handle other PSR-18 client exceptions that are not network-related throw new RuntimeException(sprintf('HTTP client error occurred while sending request to %s: %s', $request->getUri(), $e->getMessage()), 0, $e); } return $this->convertFromPsr7Response($psr7Response); } /** * Merges request options with parameter options taking precedence. * * @since 0.2.0 * * @param RequestOptions|null $requestOptions Options from the Request object. * @param RequestOptions|null $parameterOptions Options passed as method parameter. * @return RequestOptions|null Merged options, or null if both are null. */ private function mergeOptions(?RequestOptions $requestOptions, ?RequestOptions $parameterOptions): ?RequestOptions { // If no options at all, return null if ($requestOptions === null && $parameterOptions === null) { return null; } // If only one set of options exists, return it if ($requestOptions === null) { return $parameterOptions; } if ($parameterOptions === null) { return $requestOptions; } // Both exist, merge them with parameter options taking precedence $merged = new RequestOptions(); // Start with request options (lower precedence) if ($requestOptions->getTimeout() !== null) { $merged->setTimeout($requestOptions->getTimeout()); } if ($requestOptions->getConnectTimeout() !== null) { $merged->setConnectTimeout($requestOptions->getConnectTimeout()); } if ($requestOptions->getMaxRedirects() !== null) { $merged->setMaxRedirects($requestOptions->getMaxRedirects()); } // Override with parameter options (higher precedence) if ($parameterOptions->getTimeout() !== null) { $merged->setTimeout($parameterOptions->getTimeout()); } if ($parameterOptions->getConnectTimeout() !== null) { $merged->setConnectTimeout($parameterOptions->getConnectTimeout()); } if ($parameterOptions->getMaxRedirects() !== null) { $merged->setMaxRedirects($parameterOptions->getMaxRedirects()); } return $merged; } /** * Determines if the underlying client matches the Guzzle client shape. * * @since 0.2.0 * * @param ClientInterface $client The HTTP client instance. * @return bool True when the client exposes Guzzle's send signature. */ private function isGuzzleClient(ClientInterface $client): bool { $reflection = new \ReflectionObject($client); if (!is_callable([$client, 'send'])) { return \false; } if (!$reflection->hasMethod('send')) { return \false; } $method = $reflection->getMethod('send'); if (!$method->isPublic() || $method->isStatic()) { return \false; } $parameters = $method->getParameters(); if (count($parameters) < 2) { return \false; } $firstParameter = $parameters[0]->getType(); if (!$firstParameter instanceof \ReflectionNamedType || $firstParameter->isBuiltin()) { return \false; } if (!is_a($firstParameter->getName(), RequestInterface::class, \true)) { return \false; } $secondParameter = $parameters[1]; $secondType = $secondParameter->getType(); if (!$secondType instanceof \ReflectionNamedType || $secondType->getName() !== 'array') { return \false; } return \true; } /** * Sends a request using a Guzzle-compatible client. * * @since 0.2.0 * * @param RequestInterface $request The PSR-7 request to send. * @param RequestOptions $options The request options. * @return ResponseInterface The PSR-7 response received. */ private function sendWithGuzzle(RequestInterface $request, RequestOptions $options): ResponseInterface { $guzzleOptions = $this->buildGuzzleOptions($options); /** @var callable $callable */ $callable = [$this->client, 'send']; /** @var ResponseInterface $response */ $response = $callable($request, $guzzleOptions); return $response; } /** * Converts request options to a Guzzle-compatible options array. * * @since 0.2.0 * * @param RequestOptions $options The request options. * @return array<string, mixed> Guzzle-compatible options. */ private function buildGuzzleOptions(RequestOptions $options): array { $guzzleOptions = []; $timeout = $options->getTimeout(); if ($timeout !== null) { $guzzleOptions['timeout'] = $timeout; } $connectTimeout = $options->getConnectTimeout(); if ($connectTimeout !== null) { $guzzleOptions['connect_timeout'] = $connectTimeout; } $allowRedirects = $options->allowsRedirects(); if ($allowRedirects !== null) { if ($allowRedirects) { $redirectOptions = []; $maxRedirects = $options->getMaxRedirects(); if ($maxRedirects !== null) { $redirectOptions['max'] = $maxRedirects; } $guzzleOptions['allow_redirects'] = !empty($redirectOptions) ? $redirectOptions : \true; } else { $guzzleOptions['allow_redirects'] = \false; } } return $guzzleOptions; } /** * Converts a custom Request to a PSR-7 request. * * @since 0.1.0 * * @param Request $request The custom request. * @return RequestInterface The PSR-7 request. */ private function convertToPsr7Request(Request $request): RequestInterface { $psr7Request = $this->requestFactory->createRequest($request->getMethod()->value, $request->getUri()); // Add headers foreach ($request->getHeaders() as $name => $values) { foreach ($values as $value) { $psr7Request = $psr7Request->withAddedHeader($name, $value); } } // Add body if present $body = $request->getBody(); if ($body !== null) { $stream = $this->streamFactory->createStream($body); $psr7Request = $psr7Request->withBody($stream); } return $psr7Request; } /** * Converts a PSR-7 response to a custom Response. * * @since 0.1.0 * * @param ResponseInterface $psr7Response The PSR-7 response. * @return Response The custom response. */ private function convertFromPsr7Response(ResponseInterface $psr7Response): Response { $body = (string) $psr7Response->getBody(); // PSR-7 always returns headers as arrays, but HeadersCollection handles this return new Response( $psr7Response->getStatusCode(), $psr7Response->getHeaders(), // @phpstan-ignore-line $body === '' ? null : $body ); } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Http/Exception/ResponseException.php������������������������������������������������������0000644�����������������00000003041�15250554011�0015560 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\Exception; use WordPress\AiClient\Common\Exception\RuntimeException; /** * Exception class for HTTP response errors. * * This is used when response data is unexpected or malformed, * typically indicating that a provider changed in ways our code * is not aware of or when parsing response data fails. * * @since 0.1.0 */ class ResponseException extends RuntimeException { /** * Creates a ResponseException for missing expected data. * * @since 0.2.0 * * @param string $apiName The name of the API/provider. * @param string $fieldName The field that was expected but missing. * @return self */ public static function fromMissingData(string $apiName, string $fieldName): self { $message = sprintf('Unexpected %s API response: Missing the "%s" key.', $apiName, $fieldName); return new self($message); } /** * Creates a ResponseException from invalid data in an API response. * * @since 0.2.0 * * @param string $apiName The name of the API service (e.g., 'OpenAI', 'Anthropic'). * @param string $fieldName The field that was invalid. * @param string $message The specific error message describing the invalid data. * @return self */ public static function fromInvalidData(string $apiName, string $fieldName, string $message): self { return new self(sprintf('Unexpected %s API response: Invalid "%s" key: %s', $apiName, $fieldName, $message)); } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Http/Exception/ServerException.php��������������������������������������������������������0000644�����������������00000003425�15250554011�0015236 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\Exception; use WordPress\AiClient\Common\Exception\RuntimeException; use WordPress\AiClient\Providers\Http\DTO\Response; use WordPress\AiClient\Providers\Http\Util\ErrorMessageExtractor; /** * Exception thrown for 5xx HTTP server errors. * * This represents errors where the server failed to fulfill * a valid request due to internal server errors. * * @since 0.2.0 */ class ServerException extends RuntimeException { /** * Creates a ServerException from a server error response. * * This method extracts error details from common API response formats * and creates an exception with a descriptive message and status code. * * @since 0.2.0 * * @param Response $response The HTTP response that failed. * @return self */ public static function fromServerErrorResponse(Response $response): self { $statusCode = $response->getStatusCode(); $statusTexts = [500 => 'Internal Server Error', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Timeout', 507 => 'Insufficient Storage', 529 => 'Overloaded']; if (isset($statusTexts[$statusCode])) { $errorMessage = sprintf('%s (%d)', $statusTexts[$statusCode], $statusCode); } else { $errorMessage = sprintf('Server error (%d): Request was rejected due to server-side issue', $statusCode); } // Extract error message from response data using centralized utility $extractedError = ErrorMessageExtractor::extractFromResponseData($response->getData()); if ($extractedError !== null) { $errorMessage .= ' - ' . $extractedError; } return new self($errorMessage, $response->getStatusCode()); } } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Http/Exception/NetworkException.php�������������������������������������������������������0000644�����������������00000003512�15250554011�0015416 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\Exception; use WordPress\AiClientDependencies\Psr\Http\Message\RequestInterface; use WordPress\AiClient\Common\Exception\RuntimeException; use WordPress\AiClient\Providers\Http\DTO\Request; /** * Exception thrown for network-related errors. * * This includes HTTP transport errors, connection failures, * timeouts, and other network-related issues. * * @since 0.2.0 */ class NetworkException extends RuntimeException { /** * The request that failed. * * @var Request|null */ protected ?Request $request = null; /** * Returns the request that failed as our Request DTO. * * @since 0.2.0 * * @return Request * @throws \RuntimeException If no request is available */ public function getRequest(): Request { if ($this->request === null) { throw new \RuntimeException('Request object not available. This exception was directly instantiated. ' . 'Use a factory method that provides request context.'); } return $this->request; } /** * Creates a NetworkException from a PSR-18 network exception. * * @since 0.2.0 * * @param RequestInterface $psrRequest The PSR-7 request that failed. * @param \Throwable $networkException The PSR-18 network exception. * @return self */ public static function fromPsr18NetworkException(RequestInterface $psrRequest, \Throwable $networkException): self { $request = Request::fromPsrRequest($psrRequest); $message = sprintf('Network error occurred while sending request to %s: %s', $request->getUri(), $networkException->getMessage()); $exception = new self($message, 0, $networkException); $exception->request = $request; return $exception; } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Http/Exception/error_log������������������������������������������������������������������0000644�����������������00000053622�15250554011�0013321 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������[15-Jul-2026 16:16:56 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php on line 17 [15-Jul-2026 16:16:56 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\InvalidArgumentException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php on line 18 [15-Jul-2026 16:16:56 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php:16 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php on line 16 [15-Jul-2026 16:16:56 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php on line 17 [15-Jul-2026 16:16:56 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php on line 17 [24-Jul-2026 12:15:25 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php on line 17 [24-Jul-2026 12:15:26 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php on line 17 [24-Jul-2026 12:15:26 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php on line 17 [24-Jul-2026 12:15:26 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\InvalidArgumentException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php on line 18 [24-Jul-2026 12:15:26 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php:16 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php on line 16 [25-Jul-2026 05:33:29 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php on line 17 [25-Jul-2026 05:33:29 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php:16 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php on line 16 [25-Jul-2026 05:33:30 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php on line 17 [25-Jul-2026 05:33:30 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php on line 17 [25-Jul-2026 05:33:31 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\InvalidArgumentException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php on line 18 [04-Aug-2026 23:27:41 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php on line 17 [04-Aug-2026 23:27:42 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\InvalidArgumentException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php on line 18 [04-Aug-2026 23:27:42 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php on line 17 [04-Aug-2026 23:27:42 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php on line 17 [04-Aug-2026 23:27:43 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php:16 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php on line 16 [13-Aug-2026 01:33:35 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php on line 17 [13-Aug-2026 01:33:36 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\InvalidArgumentException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php on line 18 [13-Aug-2026 01:33:36 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php on line 17 [13-Aug-2026 01:33:36 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php:16 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php on line 16 [13-Aug-2026 01:33:37 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php on line 17 [13-Aug-2026 12:59:48 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php on line 17 [13-Aug-2026 12:59:48 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php:16 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php on line 16 [13-Aug-2026 12:59:48 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\InvalidArgumentException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php on line 18 [13-Aug-2026 12:59:49 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php on line 17 [13-Aug-2026 12:59:49 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php on line 17 [19-Aug-2026 21:32:06 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php:16 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php on line 16 [19-Aug-2026 21:32:06 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php on line 17 [19-Aug-2026 21:32:06 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\InvalidArgumentException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php on line 18 [19-Aug-2026 21:32:06 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php on line 17 [19-Aug-2026 21:32:06 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php on line 17 [19-Aug-2026 22:06:01 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\InvalidArgumentException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php on line 18 [19-Aug-2026 22:06:01 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php:16 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php on line 16 [19-Aug-2026 22:06:01 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php on line 17 [19-Aug-2026 22:06:02 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php on line 17 [19-Aug-2026 22:06:02 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php on line 17 [19-Aug-2026 23:23:57 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php on line 17 [19-Aug-2026 23:23:57 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php on line 17 [19-Aug-2026 23:23:57 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php on line 17 [19-Aug-2026 23:23:57 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php:16 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php on line 16 [19-Aug-2026 23:23:58 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\InvalidArgumentException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php on line 18 [04-Sep-2026 16:54:39 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\InvalidArgumentException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php on line 18 [04-Sep-2026 16:54:45 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php on line 17 [04-Sep-2026 16:54:51 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php on line 17 [04-Sep-2026 16:55:03 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php on line 17 [04-Sep-2026 16:55:04 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php:16 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php on line 16 [05-Sep-2026 19:44:27 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php on line 17 [05-Sep-2026 21:16:47 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\InvalidArgumentException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php on line 18 [05-Sep-2026 21:16:48 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php on line 17 [05-Sep-2026 21:16:49 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php on line 17 [05-Sep-2026 21:16:49 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php:16 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php on line 16 ��������������������������������������������������������������������������������������������������������������Providers/Http/Exception/RedirectException.php������������������������������������������������������0000644�����������������00000003465�15250554011�0015535 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\Exception; use WordPress\AiClient\Common\Exception\RuntimeException; use WordPress\AiClient\Providers\Http\DTO\Response; /** * Exception thrown for 3xx HTTP redirect responses. * * This represents cases where the server indicates that the request * should be retried at a different location, but automatic redirect * handling was not successful or not enabled. * * @since 0.2.0 */ class RedirectException extends RuntimeException { /** * Creates a RedirectException from a redirect response. * * This method extracts redirect information from the response headers * and creates an exception with a descriptive message and status code. * * @since 0.2.0 * * @param Response $response The HTTP redirect response. * @return self */ public static function fromRedirectResponse(Response $response): self { $statusCode = $response->getStatusCode(); $statusTexts = [300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 307 => 'Temporary Redirect', 308 => 'Permanent Redirect']; if (isset($statusTexts[$statusCode])) { $errorMessage = sprintf('%s (%d)', $statusTexts[$statusCode], $statusCode); } else { $errorMessage = sprintf('Redirect error (%d): Request needs to be retried at a different location', $statusCode); } // Try to extract the redirect location from headers $locationValues = $response->getHeader('Location'); if ($locationValues !== null && !empty($locationValues)) { $location = $locationValues[0]; $errorMessage .= ' - Location: ' . $location; } return new self($errorMessage, $statusCode); } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Http/Exception/ClientException.php��������������������������������������������������������0000644�����������������00000004655�15250554011�0015214 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\Exception; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Providers\Http\DTO\Request; use WordPress\AiClient\Providers\Http\DTO\Response; use WordPress\AiClient\Providers\Http\Util\ErrorMessageExtractor; /** * Exception thrown for 4xx HTTP client errors. * * This represents errors where the client request was malformed, * unauthorized, forbidden, or otherwise invalid. * * @since 0.2.0 */ class ClientException extends InvalidArgumentException { /** * The request that failed. * * @var Request|null */ protected ?Request $request = null; /** * Returns the request that failed as our Request DTO. * * @since 0.2.0 * * @return Request * @throws \RuntimeException If no request is available */ public function getRequest(): Request { if ($this->request === null) { throw new \RuntimeException('Request object not available. This exception was directly instantiated. ' . 'Use a factory method that provides request context.'); } return $this->request; } /** * Creates a ClientException from a client error response (4xx). * * This method extracts error details from common API response formats * and creates an exception with a descriptive message and status code. * * @since 0.2.0 * * @param Response $response The HTTP response that failed. * @return self */ public static function fromClientErrorResponse(Response $response): self { $statusCode = $response->getStatusCode(); $statusTexts = [400 => 'Bad Request', 401 => 'Unauthorized', 403 => 'Forbidden', 404 => 'Not Found', 422 => 'Unprocessable Entity', 429 => 'Too Many Requests']; if (isset($statusTexts[$statusCode])) { $errorMessage = sprintf('%s (%d)', $statusTexts[$statusCode], $statusCode); } else { $errorMessage = sprintf('Client error (%d): Request was rejected due to client-side issue', $statusCode); } // Extract error message from response data using centralized utility $extractedError = ErrorMessageExtractor::extractFromResponseData($response->getData()); if ($extractedError !== null) { $errorMessage .= ' - ' . $extractedError; } return new self($errorMessage, $statusCode); } } �����������������������������������������������������������������������������������Providers/Http/Traits/WithRequestAuthenticationTrait.php��������������������������������������������0000644�����������������00000002261�15250554011�0017606 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\Traits; use WordPress\AiClient\Common\Exception\RuntimeException; use WordPress\AiClient\Providers\Http\Contracts\RequestAuthenticationInterface; /** * Trait for a class that implements WithRequestAuthenticationInterface. * * @since 0.1.0 */ trait WithRequestAuthenticationTrait { /** * @var RequestAuthenticationInterface|null The request authentication instance. */ private ?RequestAuthenticationInterface $requestAuthentication = null; /** * {@inheritDoc} * * @since 0.1.0 */ public function setRequestAuthentication(RequestAuthenticationInterface $requestAuthentication): void { $this->requestAuthentication = $requestAuthentication; } /** * {@inheritDoc} * * @since 0.1.0 */ public function getRequestAuthentication(): RequestAuthenticationInterface { if ($this->requestAuthentication === null) { throw new RuntimeException('RequestAuthenticationInterface instance not set. ' . 'Make sure you use the AiClient class for all requests.'); } return $this->requestAuthentication; } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Http/Traits/WithHttpTransporterTrait.php��������������������������������������������������0000644�����������������00000002106�15250554011�0016437 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\Traits; use WordPress\AiClient\Common\Exception\RuntimeException; use WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface; /** * Trait for a class that implements WithHttpTransporterInterface. * * @since 0.1.0 */ trait WithHttpTransporterTrait { /** * @var HttpTransporterInterface|null The HTTP transporter instance. */ private ?HttpTransporterInterface $httpTransporter = null; /** * {@inheritDoc} * * @since 0.1.0 */ public function setHttpTransporter(HttpTransporterInterface $httpTransporter): void { $this->httpTransporter = $httpTransporter; } /** * {@inheritDoc} * * @since 0.1.0 */ public function getHttpTransporter(): HttpTransporterInterface { if ($this->httpTransporter === null) { throw new RuntimeException('HttpTransporterInterface instance not set. Make sure you use the AiClient class for all requests.'); } return $this->httpTransporter; } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Models/TextGeneration/Contracts/TextGenerationOperationModelInterface.php�����������������0000644�����������������00000001425�15250554011�0025000 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Models\TextGeneration\Contracts; use WordPress\AiClient\Messages\DTO\Message; use WordPress\AiClient\Operations\DTO\GenerativeAiOperation; /** * Interface for models that support asynchronous text generation operations. * * Provides methods for initiating long-running text generation tasks. * * @since 0.1.0 */ interface TextGenerationOperationModelInterface { /** * Creates a text generation operation. * * @since 0.1.0 * * @param list<Message> $prompt Array of messages containing the text generation prompt. * @return GenerativeAiOperation The initiated text generation operation. */ public function generateTextOperation(array $prompt): GenerativeAiOperation; } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Models/TextGeneration/Contracts/TextGenerationModelInterface.php��������������������������0000644�����������������00000001340�15250554011�0023113 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Models\TextGeneration\Contracts; use WordPress\AiClient\Messages\DTO\Message; use WordPress\AiClient\Results\DTO\GenerativeAiResult; /** * Interface for models that support text generation. * * Provides synchronous and streaming methods for generating text from prompts. * * @since 0.1.0 */ interface TextGenerationModelInterface { /** * Generates text from a prompt. * * @since 0.1.0 * * @param list<Message> $prompt Array of messages containing the text generation prompt. * @return GenerativeAiResult Result containing generated text. */ public function generateTextResult(array $prompt): GenerativeAiResult; } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Models/DTO/RequiredOption.php�������������������������������������������������������������0000644�����������������00000005513�15250554011�0014056 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Models\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; use WordPress\AiClient\Providers\Models\Enums\OptionEnum; /** * Represents an option that the implementing code requires the model to support. * * This class defines an option that the model must support with a specific value * for it to be considered suitable for the implementing code's requirements. * * @since 0.1.0 * * @phpstan-type RequiredOptionArrayShape array{ * name: string, * value: mixed * } * * @extends AbstractDataTransferObject<RequiredOptionArrayShape> */ class RequiredOption extends AbstractDataTransferObject { public const KEY_NAME = 'name'; public const KEY_VALUE = 'value'; /** * @var OptionEnum The option name. */ protected OptionEnum $name; /** * @var mixed The value that the model must support for this option. */ protected $value; /** * Constructor. * * @since 0.1.0 * * @param OptionEnum $name The option name. * @param mixed $value The value that the model must support for this option. */ public function __construct(OptionEnum $name, $value) { $this->name = $name; $this->value = $value; } /** * Gets the option name. * * @since 0.1.0 * * @return OptionEnum The option name. */ public function getName(): OptionEnum { return $this->name; } /** * Gets the value that the model must support for this option. * * @since 0.1.0 * * @return mixed The value that the model must support. */ public function getValue() { return $this->value; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_NAME => ['type' => 'string', 'enum' => OptionEnum::getValues(), 'description' => 'The option name.'], self::KEY_VALUE => ['oneOf' => [['type' => 'string'], ['type' => 'number'], ['type' => 'boolean'], ['type' => 'null'], ['type' => 'array'], ['type' => 'object']], 'description' => 'The value that the model must support for this option.']], 'required' => [self::KEY_NAME, self::KEY_VALUE]]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return RequiredOptionArrayShape */ public function toArray(): array { return [self::KEY_NAME => $this->name->value, self::KEY_VALUE => $this->value]; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function fromArray(array $array): self { static::validateFromArrayData($array, [self::KEY_NAME, self::KEY_VALUE]); return new self(OptionEnum::from($array[self::KEY_NAME]), $array[self::KEY_VALUE]); } } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Models/DTO/ModelRequirements.php����������������������������������������������������������0000644�����������������00000036511�15250554011�0014553 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Models\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Messages\DTO\Message; use WordPress\AiClient\Messages\Enums\ModalityEnum; use WordPress\AiClient\Providers\Models\Enums\CapabilityEnum; use WordPress\AiClient\Providers\Models\Enums\OptionEnum; /** * Represents requirements that implementing code has for AI model selection. * * This class defines the capabilities and options that a model must support * in order to be considered suitable for the implementing code's needs. * * @since 0.1.0 * * @phpstan-import-type RequiredOptionArrayShape from RequiredOption * * @phpstan-type ModelRequirementsArrayShape array{ * requiredCapabilities: list<string>, * requiredOptions: list<RequiredOptionArrayShape> * } * * @extends AbstractDataTransferObject<ModelRequirementsArrayShape> */ class ModelRequirements extends AbstractDataTransferObject { public const KEY_REQUIRED_CAPABILITIES = 'requiredCapabilities'; public const KEY_REQUIRED_OPTIONS = 'requiredOptions'; /** * @var list<CapabilityEnum> The capabilities that the model must support. */ protected array $requiredCapabilities; /** * @var list<RequiredOption> The options that the model must support with specific values. */ protected array $requiredOptions; /** * Constructor. * * @since 0.1.0 * * @param list<CapabilityEnum> $requiredCapabilities The capabilities that the model must support. * @param list<RequiredOption> $requiredOptions The options that the model must support with specific values. * * @throws InvalidArgumentException If arrays are not lists. */ public function __construct(array $requiredCapabilities, array $requiredOptions) { if (!array_is_list($requiredCapabilities)) { throw new InvalidArgumentException('Required capabilities must be a list array.'); } if (!array_is_list($requiredOptions)) { throw new InvalidArgumentException('Required options must be a list array.'); } $this->requiredCapabilities = $requiredCapabilities; $this->requiredOptions = $requiredOptions; } /** * Gets the capabilities that the model must support. * * @since 0.1.0 * * @return list<CapabilityEnum> The required capabilities. */ public function getRequiredCapabilities(): array { return $this->requiredCapabilities; } /** * Gets the options that the model must support with specific values. * * @since 0.1.0 * * @return list<RequiredOption> The required options. */ public function getRequiredOptions(): array { return $this->requiredOptions; } /** * Checks whether the given model metadata meets these requirements. * * @since 0.2.0 * * @param ModelMetadata $metadata The model metadata to check against. * @return bool True if the model meets all requirements, false otherwise. */ public function areMetBy(\WordPress\AiClient\Providers\Models\DTO\ModelMetadata $metadata): bool { // Create lookup maps for better performance (instead of nested foreach loops) $capabilitiesMap = []; foreach ($metadata->getSupportedCapabilities() as $capability) { $capabilitiesMap[$capability->value] = $capability; } $optionsMap = []; foreach ($metadata->getSupportedOptions() as $option) { $optionsMap[$option->getName()->value] = $option; } // Check if all required capabilities are supported using map lookup foreach ($this->requiredCapabilities as $requiredCapability) { if (!isset($capabilitiesMap[$requiredCapability->value])) { return \false; } } // Check if all required options are supported with the specified values foreach ($this->requiredOptions as $requiredOption) { // Use map lookup instead of linear search if (!isset($optionsMap[$requiredOption->getName()->value])) { return \false; } $supportedOption = $optionsMap[$requiredOption->getName()->value]; // Check if the required value is supported by this option if (!$supportedOption->isSupportedValue($requiredOption->getValue())) { return \false; } } return \true; } /** * Creates ModelRequirements from prompt data and model configuration. * * @since 0.2.0 * * @param CapabilityEnum $capability The capability the model must support. * @param list<Message> $messages The messages in the conversation. * @param ModelConfig $modelConfig The model configuration. * @return self The created requirements. */ public static function fromPromptData(CapabilityEnum $capability, array $messages, \WordPress\AiClient\Providers\Models\DTO\ModelConfig $modelConfig): self { // Start with base capability $capabilities = [$capability]; $inputModalities = []; // Check if we have chat history (multiple messages) if (count($messages) > 1) { $capabilities[] = CapabilityEnum::chatHistory(); } // Analyze all messages to determine required input modalities $hasFunctionMessageParts = \false; foreach ($messages as $message) { foreach ($message->getParts() as $part) { // Check for text input if ($part->getType()->isText()) { $inputModalities[] = ModalityEnum::text(); } // Check for file inputs if ($part->getType()->isFile()) { $file = $part->getFile(); if ($file !== null) { if ($file->isImage()) { $inputModalities[] = ModalityEnum::image(); } elseif ($file->isAudio()) { $inputModalities[] = ModalityEnum::audio(); } elseif ($file->isVideo()) { $inputModalities[] = ModalityEnum::video(); } elseif ($file->isDocument() || $file->isText()) { $inputModalities[] = ModalityEnum::document(); } } } // Check for function calls/responses (these might require special capabilities) if ($part->getType()->isFunctionCall() || $part->getType()->isFunctionResponse()) { $hasFunctionMessageParts = \true; } } } // Convert ModelConfig to RequiredOptions $requiredOptions = self::toRequiredOptions($modelConfig); // Add additional options based on message analysis if ($hasFunctionMessageParts) { $requiredOptions = self::includeInRequiredOptions($requiredOptions, new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::functionDeclarations(), \true)); } // Add input modalities if we have any inputs if (!empty($inputModalities)) { // Remove duplicates $inputModalities = array_unique($inputModalities, \SORT_REGULAR); $requiredOptions = self::includeInRequiredOptions($requiredOptions, new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::inputModalities(), array_values($inputModalities))); } // Step 6: Return new ModelRequirements return new self($capabilities, $requiredOptions); } /** * Converts ModelConfig to an array of RequiredOptions. * * @since 0.2.0 * * @param ModelConfig $modelConfig The model configuration. * @return list<RequiredOption> The required options. */ private static function toRequiredOptions(\WordPress\AiClient\Providers\Models\DTO\ModelConfig $modelConfig): array { $requiredOptions = []; // Map properties that have corresponding OptionEnum values if ($modelConfig->getOutputModalities() !== null) { $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::outputModalities(), $modelConfig->getOutputModalities()); } if ($modelConfig->getSystemInstruction() !== null) { $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::systemInstruction(), $modelConfig->getSystemInstruction()); } if ($modelConfig->getCandidateCount() !== null) { $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::candidateCount(), $modelConfig->getCandidateCount()); } if ($modelConfig->getMaxTokens() !== null) { $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::maxTokens(), $modelConfig->getMaxTokens()); } if ($modelConfig->getTemperature() !== null) { $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::temperature(), $modelConfig->getTemperature()); } if ($modelConfig->getTopP() !== null) { $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::topP(), $modelConfig->getTopP()); } if ($modelConfig->getTopK() !== null) { $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::topK(), $modelConfig->getTopK()); } if ($modelConfig->getOutputMimeType() !== null) { $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::outputMimeType(), $modelConfig->getOutputMimeType()); } if ($modelConfig->getOutputSchema() !== null) { $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::outputSchema(), $modelConfig->getOutputSchema()); } // Handle properties without OptionEnum values as custom options if ($modelConfig->getStopSequences() !== null) { $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::stopSequences(), $modelConfig->getStopSequences()); } if ($modelConfig->getPresencePenalty() !== null) { $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::presencePenalty(), $modelConfig->getPresencePenalty()); } if ($modelConfig->getFrequencyPenalty() !== null) { $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::frequencyPenalty(), $modelConfig->getFrequencyPenalty()); } if ($modelConfig->getLogprobs() !== null) { $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::logprobs(), $modelConfig->getLogprobs()); } if ($modelConfig->getTopLogprobs() !== null) { $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::topLogprobs(), $modelConfig->getTopLogprobs()); } if ($modelConfig->getFunctionDeclarations() !== null) { $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::functionDeclarations(), \true); } if ($modelConfig->getWebSearch() !== null) { $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::webSearch(), \true); } if ($modelConfig->getOutputFileType() !== null) { $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::outputFileType(), $modelConfig->getOutputFileType()); } if ($modelConfig->getOutputMediaOrientation() !== null) { $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::outputMediaOrientation(), $modelConfig->getOutputMediaOrientation()); } if ($modelConfig->getOutputMediaAspectRatio() !== null) { $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::outputMediaAspectRatio(), $modelConfig->getOutputMediaAspectRatio()); } // Add custom options as individual RequiredOptions foreach ($modelConfig->getCustomOptions() as $key => $value) { $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::customOptions(), [$key => $value]); } return $requiredOptions; } /** * Includes a RequiredOption in the array, ensuring no duplicates based on option name. * * @since 0.2.0 * * @param list<RequiredOption> $requiredOptions The existing required options. * @param RequiredOption $newOption The new option to include. * @return list<RequiredOption> The updated required options array. */ private static function includeInRequiredOptions(array $requiredOptions, \WordPress\AiClient\Providers\Models\DTO\RequiredOption $newOption): array { // Check if we already have this option name foreach ($requiredOptions as $index => $existingOption) { if ($existingOption->getName()->equals($newOption->getName())) { // Replace existing option with new one $requiredOptions[$index] = $newOption; return $requiredOptions; } } // Option not found, add it $requiredOptions[] = $newOption; return $requiredOptions; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_REQUIRED_CAPABILITIES => ['type' => 'array', 'items' => ['type' => 'string', 'enum' => CapabilityEnum::getValues()], 'description' => 'The capabilities that the model must support.'], self::KEY_REQUIRED_OPTIONS => ['type' => 'array', 'items' => \WordPress\AiClient\Providers\Models\DTO\RequiredOption::getJsonSchema(), 'description' => 'The options that the model must support with specific values.']], 'required' => [self::KEY_REQUIRED_CAPABILITIES, self::KEY_REQUIRED_OPTIONS]]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return ModelRequirementsArrayShape */ public function toArray(): array { return [self::KEY_REQUIRED_CAPABILITIES => array_map(static fn(CapabilityEnum $capability): string => $capability->value, $this->requiredCapabilities), self::KEY_REQUIRED_OPTIONS => array_map(static fn(\WordPress\AiClient\Providers\Models\DTO\RequiredOption $option): array => $option->toArray(), $this->requiredOptions)]; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function fromArray(array $array): self { static::validateFromArrayData($array, [self::KEY_REQUIRED_CAPABILITIES, self::KEY_REQUIRED_OPTIONS]); return new self(array_map(static fn(string $capability): CapabilityEnum => CapabilityEnum::from($capability), $array[self::KEY_REQUIRED_CAPABILITIES]), array_map(static fn(array $optionData): \WordPress\AiClient\Providers\Models\DTO\RequiredOption => \WordPress\AiClient\Providers\Models\DTO\RequiredOption::fromArray($optionData), $array[self::KEY_REQUIRED_OPTIONS])); } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Models/DTO/ModelMetadata.php��������������������������������������������������������������0000644�����������������00000013770�15250554011�0013612 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Models\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Providers\Models\Enums\CapabilityEnum; /** * Represents metadata about an AI model. * * This class contains information about a specific AI model, including * its identifier, display name, supported capabilities, and configuration options. * * @since 0.1.0 * * @phpstan-import-type SupportedOptionArrayShape from SupportedOption * * @phpstan-type ModelMetadataArrayShape array{ * id: string, * name: string, * supportedCapabilities: list<string>, * supportedOptions: list<SupportedOptionArrayShape> * } * * @extends AbstractDataTransferObject<ModelMetadataArrayShape> */ class ModelMetadata extends AbstractDataTransferObject { public const KEY_ID = 'id'; public const KEY_NAME = 'name'; public const KEY_SUPPORTED_CAPABILITIES = 'supportedCapabilities'; public const KEY_SUPPORTED_OPTIONS = 'supportedOptions'; /** * @var string The model's unique identifier. */ protected string $id; /** * @var string The model's display name. */ protected string $name; /** * @var list<CapabilityEnum> The model's supported capabilities. */ protected array $supportedCapabilities; /** * @var list<SupportedOption> The model's supported configuration options. */ protected array $supportedOptions; /** * Constructor. * * @since 0.1.0 * * @param string $id The model's unique identifier. * @param string $name The model's display name. * @param list<CapabilityEnum> $supportedCapabilities The model's supported capabilities. * @param list<SupportedOption> $supportedOptions The model's supported configuration options. * * @throws InvalidArgumentException If arrays are not lists. */ public function __construct(string $id, string $name, array $supportedCapabilities, array $supportedOptions) { if (!array_is_list($supportedCapabilities)) { throw new InvalidArgumentException('Supported capabilities must be a list array.'); } if (!array_is_list($supportedOptions)) { throw new InvalidArgumentException('Supported options must be a list array.'); } $this->id = $id; $this->name = $name; $this->supportedCapabilities = $supportedCapabilities; $this->supportedOptions = $supportedOptions; } /** * Gets the model's unique identifier. * * @since 0.1.0 * * @return string The model ID. */ public function getId(): string { return $this->id; } /** * Gets the model's display name. * * @since 0.1.0 * * @return string The model name. */ public function getName(): string { return $this->name; } /** * Gets the model's supported capabilities. * * @since 0.1.0 * * @return list<CapabilityEnum> The supported capabilities. */ public function getSupportedCapabilities(): array { return $this->supportedCapabilities; } /** * Gets the model's supported configuration options. * * @since 0.1.0 * * @return list<SupportedOption> The supported options. */ public function getSupportedOptions(): array { return $this->supportedOptions; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_ID => ['type' => 'string', 'description' => 'The model\'s unique identifier.'], self::KEY_NAME => ['type' => 'string', 'description' => 'The model\'s display name.'], self::KEY_SUPPORTED_CAPABILITIES => ['type' => 'array', 'items' => ['type' => 'string', 'enum' => CapabilityEnum::getValues()], 'description' => 'The model\'s supported capabilities.'], self::KEY_SUPPORTED_OPTIONS => ['type' => 'array', 'items' => \WordPress\AiClient\Providers\Models\DTO\SupportedOption::getJsonSchema(), 'description' => 'The model\'s supported configuration options.']], 'required' => [self::KEY_ID, self::KEY_NAME, self::KEY_SUPPORTED_CAPABILITIES, self::KEY_SUPPORTED_OPTIONS]]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return ModelMetadataArrayShape */ public function toArray(): array { return [self::KEY_ID => $this->id, self::KEY_NAME => $this->name, self::KEY_SUPPORTED_CAPABILITIES => array_map(static fn(CapabilityEnum $capability): string => $capability->value, $this->supportedCapabilities), self::KEY_SUPPORTED_OPTIONS => array_map(static fn(\WordPress\AiClient\Providers\Models\DTO\SupportedOption $option): array => $option->toArray(), $this->supportedOptions)]; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function fromArray(array $array): self { static::validateFromArrayData($array, [self::KEY_ID, self::KEY_NAME, self::KEY_SUPPORTED_CAPABILITIES, self::KEY_SUPPORTED_OPTIONS]); return new self($array[self::KEY_ID], $array[self::KEY_NAME], array_map(static fn(string $capability): CapabilityEnum => CapabilityEnum::from($capability), $array[self::KEY_SUPPORTED_CAPABILITIES]), array_map(static fn(array $optionData): \WordPress\AiClient\Providers\Models\DTO\SupportedOption => \WordPress\AiClient\Providers\Models\DTO\SupportedOption::fromArray($optionData), $array[self::KEY_SUPPORTED_OPTIONS])); } /** * Performs a deep clone of the model metadata. * * This method ensures that supported option objects are cloned to prevent * modifications to the cloned metadata from affecting the original. * * @since 0.4.2 */ public function __clone() { $clonedOptions = []; foreach ($this->supportedOptions as $option) { $clonedOptions[] = clone $option; } $this->supportedOptions = $clonedOptions; } } ��������Providers/Models/DTO/error_log����������������������������������������������������������������������0000644�����������������00000052246�15250554011�0012316 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������[15-Jul-2026 16:16:51 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php on line 23 [15-Jul-2026 16:16:51 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php:51 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php on line 51 [15-Jul-2026 16:16:52 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php:29 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php on line 29 [15-Jul-2026 16:16:52 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php:28 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php on line 28 [15-Jul-2026 16:16:52 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php on line 25 [24-Jul-2026 12:15:30 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php:28 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php on line 28 [24-Jul-2026 12:15:31 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php:51 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php on line 51 [24-Jul-2026 12:15:31 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php on line 25 [24-Jul-2026 12:15:31 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php on line 23 [24-Jul-2026 12:15:31 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php:29 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php on line 29 [25-Jul-2026 05:33:16 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php:51 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php on line 51 [25-Jul-2026 05:33:16 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php on line 25 [25-Jul-2026 05:33:16 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php:28 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php on line 28 [25-Jul-2026 05:33:17 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php on line 23 [25-Jul-2026 05:33:17 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php:29 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php on line 29 [04-Aug-2026 23:27:44 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php:28 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php on line 28 [04-Aug-2026 23:27:44 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php on line 25 [04-Aug-2026 23:27:44 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php on line 23 [04-Aug-2026 23:27:45 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php:51 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php on line 51 [04-Aug-2026 23:27:45 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php:29 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php on line 29 [13-Aug-2026 01:33:20 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php on line 25 [13-Aug-2026 01:33:20 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php:28 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php on line 28 [13-Aug-2026 01:33:20 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php:29 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php on line 29 [13-Aug-2026 01:33:21 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php:51 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php on line 51 [13-Aug-2026 01:33:21 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php on line 23 [13-Aug-2026 12:59:59 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php:51 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php on line 51 [13-Aug-2026 12:59:59 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php:29 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php on line 29 [13-Aug-2026 13:00:00 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php on line 23 [13-Aug-2026 13:00:00 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php:28 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php on line 28 [13-Aug-2026 13:00:00 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php on line 25 [19-Aug-2026 21:32:06 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php on line 25 [19-Aug-2026 21:32:06 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php on line 23 [19-Aug-2026 21:32:06 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php:51 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php on line 51 [19-Aug-2026 21:32:06 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php:28 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php on line 28 [19-Aug-2026 21:32:06 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php:29 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php on line 29 [19-Aug-2026 22:06:17 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php:28 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php on line 28 [19-Aug-2026 22:06:17 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php:51 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php on line 51 [19-Aug-2026 22:06:17 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php on line 25 [19-Aug-2026 22:06:17 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php on line 23 [19-Aug-2026 22:06:18 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php:29 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php on line 29 [19-Aug-2026 23:24:14 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php on line 23 [19-Aug-2026 23:24:15 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php:29 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php on line 29 [19-Aug-2026 23:24:15 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php on line 25 [19-Aug-2026 23:24:15 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php:28 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php on line 28 [19-Aug-2026 23:24:15 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php:51 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php on line 51 [04-Sep-2026 16:20:51 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php on line 23 [04-Sep-2026 16:54:36 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php:51 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php on line 51 [04-Sep-2026 16:54:39 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php:28 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php on line 28 [04-Sep-2026 17:33:14 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php on line 25 [04-Sep-2026 17:33:14 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php:29 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php on line 29 [05-Sep-2026 19:44:28 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php on line 23 [05-Sep-2026 19:44:29 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php:25 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php on line 25 [05-Sep-2026 19:44:30 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php:29 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php on line 29 [05-Sep-2026 21:16:47 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php:51 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php on line 51 [05-Sep-2026 21:16:48 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php:28 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php on line 28 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Models/DTO/SupportedOption.php������������������������������������������������������������0000644�����������������00000014011�15250554011�0014254 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Models\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; use WordPress\AiClient\Common\AbstractEnum; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Providers\Models\Enums\OptionEnum; /** * Represents a supported configuration option for an AI model. * * This class defines an option that a model supports, including its name * and the values that are valid for that option. * * @since 0.1.0 * * @phpstan-type SupportedOptionArrayShape array{ * name: string, * supportedValues?: list<mixed> * } * * @extends AbstractDataTransferObject<SupportedOptionArrayShape> */ class SupportedOption extends AbstractDataTransferObject { public const KEY_NAME = 'name'; public const KEY_SUPPORTED_VALUES = 'supportedValues'; /** * @var OptionEnum The option name. */ protected OptionEnum $name; /** * @var list<mixed>|null The supported values for this option. */ protected ?array $supportedValues; /** * Constructor. * * @since 0.1.0 * * @param OptionEnum $name The option name. * @param list<mixed>|null $supportedValues The supported values for this option, or null if any value is supported. * * @throws InvalidArgumentException If supportedValues is not null and not a list. */ public function __construct(OptionEnum $name, ?array $supportedValues = null) { if ($supportedValues !== null && !array_is_list($supportedValues)) { throw new InvalidArgumentException('Supported values must be a list array.'); } $this->name = $name; $this->supportedValues = $supportedValues; } /** * Gets the option name. * * @since 0.1.0 * * @return OptionEnum The option name. */ public function getName(): OptionEnum { return $this->name; } /** * Checks if a value is supported for this option. * * @since 0.1.0 * * @param mixed $value The value to check. * @return bool True if the value is supported, false otherwise. */ public function isSupportedValue($value): bool { // If supportedValues is null, any value is supported if ($this->supportedValues === null) { return \true; } // If the value is an array, consider it a set (i.e. order doesn't matter). if (is_array($value)) { $normalizedValue = self::normalizeArrayForComparison($value); foreach ($this->supportedValues as $supportedValue) { if (!is_array($supportedValue)) { continue; } $normalizedSupported = self::normalizeArrayForComparison($supportedValue); if ($normalizedValue === $normalizedSupported) { return \true; } } return \false; } $normalizedValue = self::normalizeValue($value); foreach ($this->supportedValues as $supportedValue) { if (self::normalizeValue($supportedValue) === $normalizedValue) { return \true; } } return \false; } /** * Normalizes an AbstractEnum instance to its string value. * * This ensures comparisons work correctly even after deserialization * (e.g. Redis/Memcached object cache), where AbstractEnum singletons * are reconstructed as separate instances. * * @since 1.2.1 * * @param mixed $value The value to normalize. * @return mixed The normalized value. */ private static function normalizeValue($value) { if ($value instanceof AbstractEnum) { return $value->value; } return $value; } /** * Normalizes and sorts an array for comparison. * * Maps each element through normalizeValue() and sorts the result, * ensuring consistent comparison regardless of element order or * AbstractEnum instance identity. * * @since 1.2.1 * * @param array<mixed> $items The array to normalize. * @return array<mixed> The normalized, sorted array. */ private static function normalizeArrayForComparison(array $items): array { $normalized = array_map([self::class, 'normalizeValue'], $items); sort($normalized); return $normalized; } /** * Gets the supported values for this option. * * @since 0.1.0 * * @return list<mixed>|null The supported values, or null if any value is supported. */ public function getSupportedValues(): ?array { return $this->supportedValues; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_NAME => ['type' => 'string', 'enum' => OptionEnum::getValues(), 'description' => 'The option name.'], self::KEY_SUPPORTED_VALUES => ['type' => 'array', 'items' => ['oneOf' => [['type' => 'string'], ['type' => 'number'], ['type' => 'boolean'], ['type' => 'null'], ['type' => 'array'], ['type' => 'object']]], 'description' => 'The supported values for this option.']], 'required' => [self::KEY_NAME]]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return SupportedOptionArrayShape */ public function toArray(): array { $data = [self::KEY_NAME => $this->name->value]; if ($this->supportedValues !== null) { /** @var list<mixed> $supportedValues */ $supportedValues = $this->supportedValues; $data[self::KEY_SUPPORTED_VALUES] = $supportedValues; } return $data; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function fromArray(array $array): self { static::validateFromArrayData($array, [self::KEY_NAME]); return new self(OptionEnum::from($array[self::KEY_NAME]), $array[self::KEY_SUPPORTED_VALUES] ?? null); } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Models/DTO/ModelConfig.php����������������������������������������������������������������0000644�����������������00000073244�15250554011�0013301 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Models\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Files\Enums\FileTypeEnum; use WordPress\AiClient\Files\Enums\MediaOrientationEnum; use WordPress\AiClient\Messages\Enums\ModalityEnum; use WordPress\AiClient\Tools\DTO\FunctionDeclaration; use WordPress\AiClient\Tools\DTO\WebSearch; /** * Represents configuration for an AI model. * * This class allows configuring various parameters for model behavior, * including output modalities, system instructions, generation parameters, * and tool integrations. * * @since 0.1.0 * * @phpstan-import-type FunctionDeclarationArrayShape from FunctionDeclaration * @phpstan-import-type WebSearchArrayShape from WebSearch * * @phpstan-type ModelConfigArrayShape array{ * outputModalities?: list<string>, * systemInstruction?: string, * candidateCount?: int, * maxTokens?: int, * temperature?: float, * topP?: float, * topK?: int, * stopSequences?: list<string>, * presencePenalty?: float, * frequencyPenalty?: float, * logprobs?: bool, * topLogprobs?: int, * functionDeclarations?: list<FunctionDeclarationArrayShape>, * webSearch?: WebSearchArrayShape, * outputFileType?: string, * outputMimeType?: string, * outputSchema?: array<string, mixed>, * outputMediaOrientation?: string, * outputMediaAspectRatio?: string, * outputSpeechVoice?: string, * customOptions?: array<string, mixed> * } * * @extends AbstractDataTransferObject<ModelConfigArrayShape> */ class ModelConfig extends AbstractDataTransferObject { public const KEY_OUTPUT_MODALITIES = 'outputModalities'; public const KEY_SYSTEM_INSTRUCTION = 'systemInstruction'; public const KEY_CANDIDATE_COUNT = 'candidateCount'; public const KEY_MAX_TOKENS = 'maxTokens'; public const KEY_TEMPERATURE = 'temperature'; public const KEY_TOP_P = 'topP'; public const KEY_TOP_K = 'topK'; public const KEY_STOP_SEQUENCES = 'stopSequences'; public const KEY_PRESENCE_PENALTY = 'presencePenalty'; public const KEY_FREQUENCY_PENALTY = 'frequencyPenalty'; public const KEY_LOGPROBS = 'logprobs'; public const KEY_TOP_LOGPROBS = 'topLogprobs'; public const KEY_FUNCTION_DECLARATIONS = 'functionDeclarations'; public const KEY_WEB_SEARCH = 'webSearch'; public const KEY_OUTPUT_FILE_TYPE = 'outputFileType'; public const KEY_OUTPUT_MIME_TYPE = 'outputMimeType'; public const KEY_OUTPUT_SCHEMA = 'outputSchema'; public const KEY_OUTPUT_MEDIA_ORIENTATION = 'outputMediaOrientation'; public const KEY_OUTPUT_MEDIA_ASPECT_RATIO = 'outputMediaAspectRatio'; public const KEY_OUTPUT_SPEECH_VOICE = 'outputSpeechVoice'; public const KEY_CUSTOM_OPTIONS = 'customOptions'; /* * Note: This key is not an actual model config key, but specified here for convenience. * It is relevant for model discovery, to determine which models support which input modalities. * The actual input modalities are part of the message sent to the model, not the model config. */ public const KEY_INPUT_MODALITIES = 'inputModalities'; /** * @var list<ModalityEnum>|null Output modalities for the model. */ protected ?array $outputModalities = null; /** * @var string|null System instruction for the model. */ protected ?string $systemInstruction = null; /** * @var int|null Number of response candidates to generate. */ protected ?int $candidateCount = null; /** * @var int|null Maximum number of tokens to generate. */ protected ?int $maxTokens = null; /** * @var float|null Temperature for randomness (0.0 to 2.0). */ protected ?float $temperature = null; /** * @var float|null Top-p nucleus sampling parameter. */ protected ?float $topP = null; /** * @var int|null Top-k sampling parameter. */ protected ?int $topK = null; /** * @var list<string>|null Stop sequences. */ protected ?array $stopSequences = null; /** * @var float|null Presence penalty for reducing repetition. */ protected ?float $presencePenalty = null; /** * @var float|null Frequency penalty for reducing repetition. */ protected ?float $frequencyPenalty = null; /** * @var bool|null Whether to return log probabilities. */ protected ?bool $logprobs = null; /** * @var int|null Number of top log probabilities to return. */ protected ?int $topLogprobs = null; /** * @var list<FunctionDeclaration>|null Function declarations available to the model. */ protected ?array $functionDeclarations = null; /** * @var WebSearch|null Web search configuration for the model. */ protected ?WebSearch $webSearch = null; /** * @var FileTypeEnum|null Output file type. */ protected ?FileTypeEnum $outputFileType = null; /** * @var string|null Output MIME type. */ protected ?string $outputMimeType = null; /** * @var array<string, mixed>|null Output schema (JSON schema). */ protected ?array $outputSchema = null; /** * @var MediaOrientationEnum|null Output media orientation. */ protected ?MediaOrientationEnum $outputMediaOrientation = null; /** * @var string|null Output media aspect ratio (e.g. 3:2, 16:9). */ protected ?string $outputMediaAspectRatio = null; /** * @var string|null Output speech voice. */ protected ?string $outputSpeechVoice = null; /** * @var array<string, mixed> Custom provider-specific options. */ protected array $customOptions = []; /** * Creates a deep clone of this configuration. * * Clones nested objects (functionDeclarations, webSearch) to ensure * the cloned configuration is independent of the original. * Enum value objects (outputModalities, outputFileType, outputMediaOrientation) * are intentionally shared as they are immutable. * * @since 0.4.2 */ public function __clone() { // Deep clone function declarations if set if ($this->functionDeclarations !== null) { $clonedDeclarations = []; foreach ($this->functionDeclarations as $declaration) { $clonedDeclarations[] = clone $declaration; } $this->functionDeclarations = $clonedDeclarations; } // Clone web search if set if ($this->webSearch !== null) { $this->webSearch = clone $this->webSearch; } // Note: Enum value objects (outputModalities, outputFileType, outputMediaOrientation) // are immutable and can be safely shared. } /** * Sets the output modalities. * * @since 0.1.0 * * @param list<ModalityEnum> $outputModalities The output modalities. * * @throws InvalidArgumentException If the array is not a list. */ public function setOutputModalities(array $outputModalities): void { if (!array_is_list($outputModalities)) { throw new InvalidArgumentException('Output modalities must be a list array.'); } $this->outputModalities = $outputModalities; } /** * Gets the output modalities. * * @since 0.1.0 * * @return list<ModalityEnum>|null The output modalities. */ public function getOutputModalities(): ?array { return $this->outputModalities; } /** * Sets the system instruction. * * @since 0.1.0 * * @param string $systemInstruction The system instruction. */ public function setSystemInstruction(string $systemInstruction): void { $this->systemInstruction = $systemInstruction; } /** * Gets the system instruction. * * @since 0.1.0 * * @return string|null The system instruction. */ public function getSystemInstruction(): ?string { return $this->systemInstruction; } /** * Sets the candidate count. * * @since 0.1.0 * * @param int $candidateCount The candidate count. */ public function setCandidateCount(int $candidateCount): void { $this->candidateCount = $candidateCount; } /** * Gets the candidate count. * * @since 0.1.0 * * @return int|null The candidate count. */ public function getCandidateCount(): ?int { return $this->candidateCount; } /** * Sets the maximum tokens. * * @since 0.1.0 * * @param int $maxTokens The maximum tokens. */ public function setMaxTokens(int $maxTokens): void { $this->maxTokens = $maxTokens; } /** * Gets the maximum tokens. * * @since 0.1.0 * * @return int|null The maximum tokens. */ public function getMaxTokens(): ?int { return $this->maxTokens; } /** * Sets the temperature. * * @since 0.1.0 * * @param float $temperature The temperature. */ public function setTemperature(float $temperature): void { $this->temperature = $temperature; } /** * Gets the temperature. * * @since 0.1.0 * * @return float|null The temperature. */ public function getTemperature(): ?float { return $this->temperature; } /** * Sets the top-p parameter. * * @since 0.1.0 * * @param float $topP The top-p parameter. */ public function setTopP(float $topP): void { $this->topP = $topP; } /** * Gets the top-p parameter. * * @since 0.1.0 * * @return float|null The top-p parameter. */ public function getTopP(): ?float { return $this->topP; } /** * Sets the top-k parameter. * * @since 0.1.0 * * @param int $topK The top-k parameter. */ public function setTopK(int $topK): void { $this->topK = $topK; } /** * Gets the top-k parameter. * * @since 0.1.0 * * @return int|null The top-k parameter. */ public function getTopK(): ?int { return $this->topK; } /** * Sets the stop sequences. * * @since 0.1.0 * * @param list<string> $stopSequences The stop sequences. * * @throws InvalidArgumentException If the array is not a list. */ public function setStopSequences(array $stopSequences): void { if (!array_is_list($stopSequences)) { throw new InvalidArgumentException('Stop sequences must be a list array.'); } $this->stopSequences = $stopSequences; } /** * Gets the stop sequences. * * @since 0.1.0 * * @return list<string>|null The stop sequences. */ public function getStopSequences(): ?array { return $this->stopSequences; } /** * Sets the presence penalty. * * @since 0.1.0 * * @param float $presencePenalty The presence penalty. */ public function setPresencePenalty(float $presencePenalty): void { $this->presencePenalty = $presencePenalty; } /** * Gets the presence penalty. * * @since 0.1.0 * * @return float|null The presence penalty. */ public function getPresencePenalty(): ?float { return $this->presencePenalty; } /** * Sets the frequency penalty. * * @since 0.1.0 * * @param float $frequencyPenalty The frequency penalty. */ public function setFrequencyPenalty(float $frequencyPenalty): void { $this->frequencyPenalty = $frequencyPenalty; } /** * Gets the frequency penalty. * * @since 0.1.0 * * @return float|null The frequency penalty. */ public function getFrequencyPenalty(): ?float { return $this->frequencyPenalty; } /** * Sets whether to return log probabilities. * * @since 0.1.0 * * @param bool $logprobs Whether to return log probabilities. */ public function setLogprobs(bool $logprobs): void { $this->logprobs = $logprobs; } /** * Gets whether to return log probabilities. * * @since 0.1.0 * * @return bool|null Whether to return log probabilities. */ public function getLogprobs(): ?bool { return $this->logprobs; } /** * Sets the number of top log probabilities to return. * * @since 0.1.0 * * @param int $topLogprobs The number of top log probabilities. */ public function setTopLogprobs(int $topLogprobs): void { $this->topLogprobs = $topLogprobs; } /** * Gets the number of top log probabilities to return. * * @since 0.1.0 * * @return int|null The number of top log probabilities. */ public function getTopLogprobs(): ?int { return $this->topLogprobs; } /** * Sets the function declarations. * * @since 0.1.0 * * @param list<FunctionDeclaration> $functionDeclarations The function declarations. * * @throws InvalidArgumentException If the array is not a list. */ public function setFunctionDeclarations(array $functionDeclarations): void { if (!array_is_list($functionDeclarations)) { throw new InvalidArgumentException('Function declarations must be a list array.'); } $this->functionDeclarations = $functionDeclarations; } /** * Gets the function declarations. * * @since 0.1.0 * * @return list<FunctionDeclaration>|null The function declarations. */ public function getFunctionDeclarations(): ?array { return $this->functionDeclarations; } /** * Sets the web search configuration. * * @since 0.1.0 * * @param WebSearch $webSearch The web search configuration. */ public function setWebSearch(WebSearch $webSearch): void { $this->webSearch = $webSearch; } /** * Gets the web search configuration. * * @since 0.1.0 * * @return WebSearch|null The web search configuration. */ public function getWebSearch(): ?WebSearch { return $this->webSearch; } /** * Sets the output file type. * * @since 0.1.0 * * @param FileTypeEnum $outputFileType The output file type. */ public function setOutputFileType(FileTypeEnum $outputFileType): void { $this->outputFileType = $outputFileType; } /** * Gets the output file type. * * @since 0.1.0 * * @return FileTypeEnum|null The output file type. */ public function getOutputFileType(): ?FileTypeEnum { return $this->outputFileType; } /** * Sets the output MIME type. * * @since 0.1.0 * * @param string $outputMimeType The output MIME type. */ public function setOutputMimeType(string $outputMimeType): void { $this->outputMimeType = $outputMimeType; } /** * Gets the output MIME type. * * @since 0.1.0 * * @return string|null The output MIME type. */ public function getOutputMimeType(): ?string { return $this->outputMimeType; } /** * Sets the output schema. * * When setting an output schema, this method automatically sets * the output MIME type to "application/json" if not already set. * * @since 0.1.0 * * @param array<string, mixed> $outputSchema The output schema (JSON schema). */ public function setOutputSchema(array $outputSchema): void { $this->outputSchema = $outputSchema; // Automatically set outputMimeType to application/json when schema is provided if ($this->outputMimeType === null) { $this->outputMimeType = 'application/json'; } } /** * Gets the output schema. * * @since 0.1.0 * * @return array<string, mixed>|null The output schema. */ public function getOutputSchema(): ?array { return $this->outputSchema; } /** * Sets the output media orientation. * * @since 0.1.0 * * @param MediaOrientationEnum $outputMediaOrientation The output media orientation. */ public function setOutputMediaOrientation(MediaOrientationEnum $outputMediaOrientation): void { if ($this->outputMediaAspectRatio) { $this->validateMediaOrientationAspectRatioCompatibility($outputMediaOrientation, $this->outputMediaAspectRatio); } $this->outputMediaOrientation = $outputMediaOrientation; } /** * Gets the output media orientation. * * @since 0.1.0 * * @return MediaOrientationEnum|null The output media orientation. */ public function getOutputMediaOrientation(): ?MediaOrientationEnum { return $this->outputMediaOrientation; } /** * Sets the output media aspect ratio. * * If set, this supersedes the output media orientation, as it is a more specific configuration. * * @since 0.1.0 * * @param string $outputMediaAspectRatio The output media aspect ratio (e.g. 3:2, 16:9). */ public function setOutputMediaAspectRatio(string $outputMediaAspectRatio): void { if (!preg_match('/^\d+:\d+$/', $outputMediaAspectRatio)) { throw new InvalidArgumentException('Output media aspect ratio must be in the format "width:height" (e.g. 3:2, 16:9).'); } if ($this->outputMediaOrientation) { $this->validateMediaOrientationAspectRatioCompatibility($this->outputMediaOrientation, $outputMediaAspectRatio); } $this->outputMediaAspectRatio = $outputMediaAspectRatio; } /** * Gets the output media aspect ratio. * * @since 0.1.0 * * @return string|null The output media aspect ratio (e.g. 3:2, 16:9). */ public function getOutputMediaAspectRatio(): ?string { return $this->outputMediaAspectRatio; } /** * Validates that the given media orientation and aspect ratio values do not conflict with each other. * * @since 0.4.0 * * @param MediaOrientationEnum $orientation The desired media orientation. * @param string $aspectRatio The desired media aspect ratio. */ protected function validateMediaOrientationAspectRatioCompatibility(MediaOrientationEnum $orientation, string $aspectRatio): void { $aspectRatioParts = explode(':', $aspectRatio); if ($orientation->isSquare() && $aspectRatioParts[0] !== $aspectRatioParts[1]) { throw new InvalidArgumentException('The aspect ratio "' . $aspectRatio . '" is not compatible with the square orientation.'); } if ($orientation->isLandscape() && $aspectRatioParts[0] <= $aspectRatioParts[1]) { throw new InvalidArgumentException('The aspect ratio "' . $aspectRatio . '" is not compatible with the landscape orientation.'); } if ($orientation->isPortrait() && $aspectRatioParts[0] >= $aspectRatioParts[1]) { throw new InvalidArgumentException('The aspect ratio "' . $aspectRatio . '" is not compatible with the portrait orientation.'); } } /** * Sets the output speech voice. * * @since 0.1.0 * * @param string $outputSpeechVoice The output speech voice. */ public function setOutputSpeechVoice(string $outputSpeechVoice): void { $this->outputSpeechVoice = $outputSpeechVoice; } /** * Gets the output speech voice. * * @since 0.1.0 * * @return string|null The output speech voice. */ public function getOutputSpeechVoice(): ?string { return $this->outputSpeechVoice; } /** * Sets a single custom option. * * @since 0.1.0 * * @param string $key The option key. * @param mixed $value The option value. */ public function setCustomOption(string $key, $value): void { $this->customOptions[$key] = $value; } /** * Sets the custom options. * * @since 0.1.0 * * @param array<string, mixed> $customOptions The custom options. */ public function setCustomOptions(array $customOptions): void { $this->customOptions = $customOptions; } /** * Gets the custom options. * * @since 0.1.0 * * @return array<string, mixed> The custom options. */ public function getCustomOptions(): array { return $this->customOptions; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_OUTPUT_MODALITIES => ['type' => 'array', 'items' => ['type' => 'string', 'enum' => ModalityEnum::getValues()], 'description' => 'Output modalities for the model.'], self::KEY_SYSTEM_INSTRUCTION => ['type' => 'string', 'description' => 'System instruction for the model.'], self::KEY_CANDIDATE_COUNT => ['type' => 'integer', 'minimum' => 1, 'description' => 'Number of response candidates to generate.'], self::KEY_MAX_TOKENS => ['type' => 'integer', 'minimum' => 1, 'description' => 'Maximum number of tokens to generate.'], self::KEY_TEMPERATURE => ['type' => 'number', 'minimum' => 0.0, 'maximum' => 2.0, 'description' => 'Temperature for randomness.'], self::KEY_TOP_P => ['type' => 'number', 'minimum' => 0.0, 'maximum' => 1.0, 'description' => 'Top-p nucleus sampling parameter.'], self::KEY_TOP_K => ['type' => 'integer', 'minimum' => 1, 'description' => 'Top-k sampling parameter.'], self::KEY_STOP_SEQUENCES => ['type' => 'array', 'items' => ['type' => 'string'], 'description' => 'Stop sequences.'], self::KEY_PRESENCE_PENALTY => ['type' => 'number', 'description' => 'Presence penalty for reducing repetition.'], self::KEY_FREQUENCY_PENALTY => ['type' => 'number', 'description' => 'Frequency penalty for reducing repetition.'], self::KEY_LOGPROBS => ['type' => 'boolean', 'description' => 'Whether to return log probabilities.'], self::KEY_TOP_LOGPROBS => ['type' => 'integer', 'minimum' => 1, 'description' => 'Number of top log probabilities to return.'], self::KEY_FUNCTION_DECLARATIONS => ['type' => 'array', 'items' => FunctionDeclaration::getJsonSchema(), 'description' => 'Function declarations available to the model.'], self::KEY_WEB_SEARCH => WebSearch::getJsonSchema(), self::KEY_OUTPUT_FILE_TYPE => ['type' => 'string', 'enum' => FileTypeEnum::getValues(), 'description' => 'Output file type.'], self::KEY_OUTPUT_MIME_TYPE => ['type' => 'string', 'description' => 'Output MIME type.'], self::KEY_OUTPUT_SCHEMA => ['type' => 'object', 'additionalProperties' => \true, 'description' => 'Output schema (JSON schema).'], self::KEY_OUTPUT_MEDIA_ORIENTATION => ['type' => 'string', 'enum' => MediaOrientationEnum::getValues(), 'description' => 'Output media orientation.'], self::KEY_OUTPUT_MEDIA_ASPECT_RATIO => ['type' => 'string', 'pattern' => '^\d+:\d+$', 'description' => 'Output media aspect ratio.'], self::KEY_OUTPUT_SPEECH_VOICE => ['type' => 'string', 'description' => 'Output speech voice.'], self::KEY_CUSTOM_OPTIONS => ['type' => 'object', 'additionalProperties' => \true, 'description' => 'Custom provider-specific options.']], 'additionalProperties' => \false]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return ModelConfigArrayShape */ public function toArray(): array { $data = []; if ($this->outputModalities !== null) { $data[self::KEY_OUTPUT_MODALITIES] = array_map(static function (ModalityEnum $modality): string { return $modality->value; }, $this->outputModalities); } if ($this->systemInstruction !== null) { $data[self::KEY_SYSTEM_INSTRUCTION] = $this->systemInstruction; } if ($this->candidateCount !== null) { $data[self::KEY_CANDIDATE_COUNT] = $this->candidateCount; } if ($this->maxTokens !== null) { $data[self::KEY_MAX_TOKENS] = $this->maxTokens; } if ($this->temperature !== null) { $data[self::KEY_TEMPERATURE] = $this->temperature; } if ($this->topP !== null) { $data[self::KEY_TOP_P] = $this->topP; } if ($this->topK !== null) { $data[self::KEY_TOP_K] = $this->topK; } if ($this->stopSequences !== null) { $data[self::KEY_STOP_SEQUENCES] = $this->stopSequences; } if ($this->presencePenalty !== null) { $data[self::KEY_PRESENCE_PENALTY] = $this->presencePenalty; } if ($this->frequencyPenalty !== null) { $data[self::KEY_FREQUENCY_PENALTY] = $this->frequencyPenalty; } if ($this->logprobs !== null) { $data[self::KEY_LOGPROBS] = $this->logprobs; } if ($this->topLogprobs !== null) { $data[self::KEY_TOP_LOGPROBS] = $this->topLogprobs; } if ($this->functionDeclarations !== null) { $data[self::KEY_FUNCTION_DECLARATIONS] = array_map(static function (FunctionDeclaration $functionDeclaration): array { return $functionDeclaration->toArray(); }, $this->functionDeclarations); } if ($this->webSearch !== null) { $data[self::KEY_WEB_SEARCH] = $this->webSearch->toArray(); } if ($this->outputFileType !== null) { $data[self::KEY_OUTPUT_FILE_TYPE] = $this->outputFileType->value; } if ($this->outputMimeType !== null) { $data[self::KEY_OUTPUT_MIME_TYPE] = $this->outputMimeType; } if ($this->outputSchema !== null) { $data[self::KEY_OUTPUT_SCHEMA] = $this->outputSchema; } if ($this->outputMediaOrientation !== null) { $data[self::KEY_OUTPUT_MEDIA_ORIENTATION] = $this->outputMediaOrientation->value; } if ($this->outputMediaAspectRatio !== null) { $data[self::KEY_OUTPUT_MEDIA_ASPECT_RATIO] = $this->outputMediaAspectRatio; } if ($this->outputSpeechVoice !== null) { $data[self::KEY_OUTPUT_SPEECH_VOICE] = $this->outputSpeechVoice; } if (!empty($this->customOptions)) { $data[self::KEY_CUSTOM_OPTIONS] = $this->customOptions; } return $data; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function fromArray(array $array): self { $config = new self(); if (isset($array[self::KEY_OUTPUT_MODALITIES])) { $config->setOutputModalities(array_map(static fn(string $modality): ModalityEnum => ModalityEnum::from($modality), $array[self::KEY_OUTPUT_MODALITIES])); } if (isset($array[self::KEY_SYSTEM_INSTRUCTION])) { $config->setSystemInstruction($array[self::KEY_SYSTEM_INSTRUCTION]); } if (isset($array[self::KEY_CANDIDATE_COUNT])) { $config->setCandidateCount($array[self::KEY_CANDIDATE_COUNT]); } if (isset($array[self::KEY_MAX_TOKENS])) { $config->setMaxTokens($array[self::KEY_MAX_TOKENS]); } if (isset($array[self::KEY_TEMPERATURE])) { $config->setTemperature($array[self::KEY_TEMPERATURE]); } if (isset($array[self::KEY_TOP_P])) { $config->setTopP($array[self::KEY_TOP_P]); } if (isset($array[self::KEY_TOP_K])) { $config->setTopK($array[self::KEY_TOP_K]); } if (isset($array[self::KEY_STOP_SEQUENCES])) { $config->setStopSequences($array[self::KEY_STOP_SEQUENCES]); } if (isset($array[self::KEY_PRESENCE_PENALTY])) { $config->setPresencePenalty($array[self::KEY_PRESENCE_PENALTY]); } if (isset($array[self::KEY_FREQUENCY_PENALTY])) { $config->setFrequencyPenalty($array[self::KEY_FREQUENCY_PENALTY]); } if (isset($array[self::KEY_LOGPROBS])) { $config->setLogprobs($array[self::KEY_LOGPROBS]); } if (isset($array[self::KEY_TOP_LOGPROBS])) { $config->setTopLogprobs($array[self::KEY_TOP_LOGPROBS]); } if (isset($array[self::KEY_FUNCTION_DECLARATIONS])) { $config->setFunctionDeclarations(array_map(static function (array $functionDeclarationData): FunctionDeclaration { return FunctionDeclaration::fromArray($functionDeclarationData); }, $array[self::KEY_FUNCTION_DECLARATIONS])); } if (isset($array[self::KEY_WEB_SEARCH])) { $config->setWebSearch(WebSearch::fromArray($array[self::KEY_WEB_SEARCH])); } if (isset($array[self::KEY_OUTPUT_FILE_TYPE])) { $config->setOutputFileType(FileTypeEnum::from($array[self::KEY_OUTPUT_FILE_TYPE])); } if (isset($array[self::KEY_OUTPUT_MIME_TYPE])) { $config->setOutputMimeType($array[self::KEY_OUTPUT_MIME_TYPE]); } if (isset($array[self::KEY_OUTPUT_SCHEMA])) { $config->setOutputSchema($array[self::KEY_OUTPUT_SCHEMA]); } if (isset($array[self::KEY_OUTPUT_MEDIA_ORIENTATION])) { $config->setOutputMediaOrientation(MediaOrientationEnum::from($array[self::KEY_OUTPUT_MEDIA_ORIENTATION])); } if (isset($array[self::KEY_OUTPUT_MEDIA_ASPECT_RATIO])) { $config->setOutputMediaAspectRatio($array[self::KEY_OUTPUT_MEDIA_ASPECT_RATIO]); } if (isset($array[self::KEY_OUTPUT_SPEECH_VOICE])) { $config->setOutputSpeechVoice($array[self::KEY_OUTPUT_SPEECH_VOICE]); } if (isset($array[self::KEY_CUSTOM_OPTIONS])) { $config->setCustomOptions($array[self::KEY_CUSTOM_OPTIONS]); } return $config; } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Models/Contracts/ModelInterface.php�������������������������������������������������������0000644�����������������00000002357�15250554011�0015303 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Models\Contracts; use WordPress\AiClient\Providers\DTO\ProviderMetadata; use WordPress\AiClient\Providers\Models\DTO\ModelConfig; use WordPress\AiClient\Providers\Models\DTO\ModelMetadata; /** * Interface for AI models. * * Models represent specific AI models from providers and define * their capabilities, configuration, and execution methods. * * @since 0.1.0 */ interface ModelInterface { /** * Gets model metadata. * * @since 0.1.0 * * @return ModelMetadata Model metadata. */ public function metadata(): ModelMetadata; /** * Returns the metadata for the model's provider. * * @since 0.1.0 * * @return ProviderMetadata The provider metadata. */ public function providerMetadata(): ProviderMetadata; /** * Sets model configuration. * * @since 0.1.0 * * @param ModelConfig $config Model configuration. * @return void */ public function setConfig(ModelConfig $config): void; /** * Gets model configuration. * * @since 0.1.0 * * @return ModelConfig Current model configuration. */ public function getConfig(): ModelConfig; } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Models/ImageGeneration/Contracts/ImageGenerationOperationModelInterface.php���������������0000644�����������������00000001436�15250554011�0025176 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Models\ImageGeneration\Contracts; use WordPress\AiClient\Messages\DTO\Message; use WordPress\AiClient\Operations\DTO\GenerativeAiOperation; /** * Interface for models that support asynchronous image generation operations. * * Provides methods for initiating long-running image generation tasks. * * @since 0.1.0 */ interface ImageGenerationOperationModelInterface { /** * Creates an image generation operation. * * @since 0.1.0 * * @param list<Message> $prompt Array of messages containing the image generation prompt. * @return GenerativeAiOperation The initiated image generation operation. */ public function generateImageOperation(array $prompt): GenerativeAiOperation; } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Models/ImageGeneration/Contracts/ImageGenerationModelInterface.php������������������������0000644�����������������00000001342�15250554011�0023311 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Models\ImageGeneration\Contracts; use WordPress\AiClient\Messages\DTO\Message; use WordPress\AiClient\Results\DTO\GenerativeAiResult; /** * Interface for models that support image generation. * * Provides synchronous methods for generating images from text prompts. * * @since 0.1.0 */ interface ImageGenerationModelInterface { /** * Generates images from a prompt. * * @since 0.1.0 * * @param list<Message> $prompt Array of messages containing the image generation prompt. * @return GenerativeAiResult Result containing generated images. */ public function generateImageResult(array $prompt): GenerativeAiResult; } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Models/Enums/OptionEnum.php���������������������������������������������������������������0000644�����������������00000013451�15250554011�0013643 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Models\Enums; use ReflectionClass; use WordPress\AiClient\Common\AbstractEnum; use WordPress\AiClient\Providers\Models\DTO\ModelConfig; /** * Enum for model options. * * This enum dynamically includes all options from ModelConfig KEY_* constants * in addition to the explicitly defined constants below. * * Explicitly defined option (not in ModelConfig): * @method static self inputModalities() Creates an instance for INPUT_MODALITIES option. * @method bool isInputModalities() Checks if the option is INPUT_MODALITIES. * * Dynamically loaded from ModelConfig KEY_* constants: * @method static self candidateCount() Creates an instance for CANDIDATE_COUNT option. * @method static self customOptions() Creates an instance for CUSTOM_OPTIONS option. * @method static self frequencyPenalty() Creates an instance for FREQUENCY_PENALTY option. * @method static self functionDeclarations() Creates an instance for FUNCTION_DECLARATIONS option. * @method static self logprobs() Creates an instance for LOGPROBS option. * @method static self maxTokens() Creates an instance for MAX_TOKENS option. * @method static self outputFileType() Creates an instance for OUTPUT_FILE_TYPE option. * @method static self outputMediaAspectRatio() Creates an instance for OUTPUT_MEDIA_ASPECT_RATIO option. * @method static self outputMediaOrientation() Creates an instance for OUTPUT_MEDIA_ORIENTATION option. * @method static self outputMimeType() Creates an instance for OUTPUT_MIME_TYPE option. * @method static self outputModalities() Creates an instance for OUTPUT_MODALITIES option. * @method static self outputSchema() Creates an instance for OUTPUT_SCHEMA option. * @method static self outputSpeechVoice() Creates an instance for OUTPUT_SPEECH_VOICE option. * @method static self presencePenalty() Creates an instance for PRESENCE_PENALTY option. * @method static self stopSequences() Creates an instance for STOP_SEQUENCES option. * @method static self systemInstruction() Creates an instance for SYSTEM_INSTRUCTION option. * @method static self temperature() Creates an instance for TEMPERATURE option. * @method static self topK() Creates an instance for TOP_K option. * @method static self topLogprobs() Creates an instance for TOP_LOGPROBS option. * @method static self topP() Creates an instance for TOP_P option. * @method static self webSearch() Creates an instance for WEB_SEARCH option. * @method bool isCandidateCount() Checks if the option is CANDIDATE_COUNT. * @method bool isCustomOptions() Checks if the option is CUSTOM_OPTIONS. * @method bool isFrequencyPenalty() Checks if the option is FREQUENCY_PENALTY. * @method bool isFunctionDeclarations() Checks if the option is FUNCTION_DECLARATIONS. * @method bool isLogprobs() Checks if the option is LOGPROBS. * @method bool isMaxTokens() Checks if the option is MAX_TOKENS. * @method bool isOutputFileType() Checks if the option is OUTPUT_FILE_TYPE. * @method bool isOutputMediaAspectRatio() Checks if the option is OUTPUT_MEDIA_ASPECT_RATIO. * @method bool isOutputMediaOrientation() Checks if the option is OUTPUT_MEDIA_ORIENTATION. * @method bool isOutputMimeType() Checks if the option is OUTPUT_MIME_TYPE. * @method bool isOutputModalities() Checks if the option is OUTPUT_MODALITIES. * @method bool isOutputSchema() Checks if the option is OUTPUT_SCHEMA. * @method bool isOutputSpeechVoice() Checks if the option is OUTPUT_SPEECH_VOICE. * @method bool isPresencePenalty() Checks if the option is PRESENCE_PENALTY. * @method bool isStopSequences() Checks if the option is STOP_SEQUENCES. * @method bool isSystemInstruction() Checks if the option is SYSTEM_INSTRUCTION. * @method bool isTemperature() Checks if the option is TEMPERATURE. * @method bool isTopK() Checks if the option is TOP_K. * @method bool isTopLogprobs() Checks if the option is TOP_LOGPROBS. * @method bool isTopP() Checks if the option is TOP_P. * @method bool isWebSearch() Checks if the option is WEB_SEARCH. * * @since 0.1.0 */ class OptionEnum extends AbstractEnum { /** * Input modalities option. * * This constant is not in ModelConfig as it's derived from message content, * not configured directly. */ public const INPUT_MODALITIES = 'input_modalities'; /** * Determines the class enumerations by reflecting on class constants. * * Overrides the parent method to dynamically add constants from ModelConfig * that are prefixed with KEY_. These are transformed to remove the KEY_ prefix * and converted to snake_case values. * * @since 0.1.0 * * @param class-string $className The fully qualified class name. * @return array<string, string> The enum constants. */ protected static function determineClassEnumerations(string $className): array { // Start with the constants defined in this class using parent method $constants = parent::determineClassEnumerations($className); // Use reflection to get all constants from ModelConfig $modelConfigReflection = new ReflectionClass(ModelConfig::class); $modelConfigConstants = $modelConfigReflection->getConstants(); // Add ModelConfig constants that start with KEY_ foreach ($modelConfigConstants as $constantName => $constantValue) { if (str_starts_with($constantName, 'KEY_')) { // Remove KEY_ prefix to get the enum constant name $enumConstantName = substr($constantName, 4); // The value is the snake_case version stored in ModelConfig // ModelConfig already stores these as snake_case strings if (is_string($constantValue)) { $constants[$enumConstantName] = $constantValue; } } } return $constants; } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Models/Enums/error_log��������������������������������������������������������������������0000644�����������������00000020250�15250554011�0012745 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������[15-Jul-2026 16:16:50 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php:65 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php on line 65 [15-Jul-2026 16:16:51 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php:29 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php on line 29 [24-Jul-2026 12:15:29 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php:65 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php on line 65 [24-Jul-2026 12:15:30 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php:29 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php on line 29 [25-Jul-2026 05:33:20 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php:29 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php on line 29 [25-Jul-2026 05:33:20 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php:65 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php on line 65 [04-Aug-2026 23:27:48 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php:65 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php on line 65 [04-Aug-2026 23:27:48 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php:29 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php on line 29 [13-Aug-2026 01:33:18 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php:29 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php on line 29 [13-Aug-2026 01:33:18 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php:65 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php on line 65 [13-Aug-2026 12:59:57 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php:29 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php on line 29 [13-Aug-2026 12:59:57 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php:65 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php on line 65 [19-Aug-2026 21:32:06 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php:29 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php on line 29 [19-Aug-2026 21:32:06 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php:65 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php on line 65 [19-Aug-2026 22:06:14 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php:65 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php on line 65 [19-Aug-2026 22:06:14 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php:29 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php on line 29 [19-Aug-2026 23:24:17 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php:29 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php on line 29 [19-Aug-2026 23:24:17 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php:65 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php on line 65 [04-Sep-2026 11:16:28 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php:29 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php on line 29 [05-Sep-2026 03:46:46 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php:65 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php on line 65 [05-Sep-2026 17:44:32 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php:29 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php on line 29 [06-Sep-2026 06:39:39 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php:65 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php on line 65 ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Models/Enums/CapabilityEnum.php�����������������������������������������������������������0000644�����������������00000005022�15250554011�0014447 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Models\Enums; use WordPress\AiClient\Common\AbstractEnum; /** * Enum for model capabilities. * * @since 0.1.0 * * @method static self textGeneration() Creates an instance for TEXT_GENERATION capability. * @method static self imageGeneration() Creates an instance for IMAGE_GENERATION capability. * @method static self textToSpeechConversion() Creates an instance for TEXT_TO_SPEECH_CONVERSION capability. * @method static self speechGeneration() Creates an instance for SPEECH_GENERATION capability. * @method static self musicGeneration() Creates an instance for MUSIC_GENERATION capability. * @method static self videoGeneration() Creates an instance for VIDEO_GENERATION capability. * @method static self embeddingGeneration() Creates an instance for EMBEDDING_GENERATION capability. * @method static self chatHistory() Creates an instance for CHAT_HISTORY capability. * @method bool isTextGeneration() Checks if the capability is TEXT_GENERATION. * @method bool isImageGeneration() Checks if the capability is IMAGE_GENERATION. * @method bool isTextToSpeechConversion() Checks if the capability is TEXT_TO_SPEECH_CONVERSION. * @method bool isSpeechGeneration() Checks if the capability is SPEECH_GENERATION. * @method bool isMusicGeneration() Checks if the capability is MUSIC_GENERATION. * @method bool isVideoGeneration() Checks if the capability is VIDEO_GENERATION. * @method bool isEmbeddingGeneration() Checks if the capability is EMBEDDING_GENERATION. * @method bool isChatHistory() Checks if the capability is CHAT_HISTORY. */ class CapabilityEnum extends AbstractEnum { /** * Text generation capability. */ public const TEXT_GENERATION = 'text_generation'; /** * Image generation capability. */ public const IMAGE_GENERATION = 'image_generation'; /** * Text to speech conversion capability. */ public const TEXT_TO_SPEECH_CONVERSION = 'text_to_speech_conversion'; /** * Speech generation capability. */ public const SPEECH_GENERATION = 'speech_generation'; /** * Music generation capability. */ public const MUSIC_GENERATION = 'music_generation'; /** * Video generation capability. */ public const VIDEO_GENERATION = 'video_generation'; /** * Embedding generation capability. */ public const EMBEDDING_GENERATION = 'embedding_generation'; /** * Chat history support capability. */ public const CHAT_HISTORY = 'chat_history'; } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Models/VideoGeneration/Contracts/VideoGenerationModelInterface.php������������������������0000644�����������������00000001335�15250554011�0023363 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Models\VideoGeneration\Contracts; use WordPress\AiClient\Messages\DTO\Message; use WordPress\AiClient\Results\DTO\GenerativeAiResult; /** * Interface for models that support video generation. * * Provides synchronous methods for generating videos from prompts. * * @since 1.3.0 */ interface VideoGenerationModelInterface { /** * Generates videos from a prompt. * * @since 1.3.0 * * @param list<Message> $prompt Array of messages containing the video generation prompt. * @return GenerativeAiResult Result containing generated videos. */ public function generateVideoResult(array $prompt): GenerativeAiResult; } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Models/VideoGeneration/Contracts/VideoGenerationOperationModelInterface.php���������������0000644�����������������00000001435�15250554011�0025245 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Models\VideoGeneration\Contracts; use WordPress\AiClient\Messages\DTO\Message; use WordPress\AiClient\Operations\DTO\GenerativeAiOperation; /** * Interface for models that support asynchronous video generation operations. * * Provides methods for initiating long-running video generation tasks. * * @since 1.3.0 */ interface VideoGenerationOperationModelInterface { /** * Creates a video generation operation. * * @since 1.3.0 * * @param list<Message> $prompt Array of messages containing the video generation prompt. * @return GenerativeAiOperation The initiated video generation operation. */ public function generateVideoOperation(array $prompt): GenerativeAiOperation; } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Models/TextToSpeechConversion/Contracts/TextToSpeechConversionModelInterface.php����������0000644�����������������00000001374�15250554011�0026274 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Models\TextToSpeechConversion\Contracts; use WordPress\AiClient\Messages\DTO\Message; use WordPress\AiClient\Results\DTO\GenerativeAiResult; /** * Interface for models that support text-to-speech conversion. * * Provides synchronous methods for converting text to speech audio. * * @since 0.1.0 */ interface TextToSpeechConversionModelInterface { /** * Converts text to speech. * * @since 0.1.0 * * @param list<Message> $prompt Array of messages containing the text to convert to speech. * @return GenerativeAiResult Result containing generated speech audio. */ public function convertTextToSpeechResult(array $prompt): GenerativeAiResult; } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Models/TextToSpeechConversion/Contracts/TextToSpeechConversionOperationModelInterface.php�0000644�����������������00000001527�15250554011�0030155 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Models\TextToSpeechConversion\Contracts; use WordPress\AiClient\Messages\DTO\Message; use WordPress\AiClient\Operations\DTO\GenerativeAiOperation; /** * Interface for models that support asynchronous text-to-speech conversion operations. * * Provides methods for initiating long-running text-to-speech conversion tasks. * * @since 0.1.0 */ interface TextToSpeechConversionOperationModelInterface { /** * Creates a text-to-speech conversion operation. * * @since 0.1.0 * * @param list<Message> $prompt Array of messages containing the text to convert to speech. * @return GenerativeAiOperation The initiated text-to-speech conversion operation. */ public function convertTextToSpeechOperation(array $prompt): GenerativeAiOperation; } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Models/SpeechGeneration/Contracts/SpeechGenerationOperationModelInterface.php�������������0000644�����������������00000001445�15250554011�0025550 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Models\SpeechGeneration\Contracts; use WordPress\AiClient\Messages\DTO\Message; use WordPress\AiClient\Operations\DTO\GenerativeAiOperation; /** * Interface for models that support asynchronous speech generation operations. * * Provides methods for initiating long-running speech generation tasks. * * @since 0.1.0 */ interface SpeechGenerationOperationModelInterface { /** * Creates a speech generation operation. * * @since 0.1.0 * * @param list<Message> $prompt Array of messages containing the speech generation prompt. * @return GenerativeAiOperation The initiated speech generation operation. */ public function generateSpeechOperation(array $prompt): GenerativeAiOperation; } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Models/SpeechGeneration/Contracts/SpeechGenerationModelInterface.php����������������������0000644�����������������00000001350�15250554011�0023662 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Models\SpeechGeneration\Contracts; use WordPress\AiClient\Messages\DTO\Message; use WordPress\AiClient\Results\DTO\GenerativeAiResult; /** * Interface for models that support speech generation. * * Provides synchronous methods for generating speech from prompts. * * @since 0.1.0 */ interface SpeechGenerationModelInterface { /** * Generates speech from a prompt. * * @since 0.1.0 * * @param list<Message> $prompt Array of messages containing the speech generation prompt. * @return GenerativeAiResult Result containing generated speech audio. */ public function generateSpeechResult(array $prompt): GenerativeAiResult; } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/error_log���������������������������������������������������������������������������������0000644�����������������00000017643�15250554011�0010447 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������[15-Jul-2026 16:16:45 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/AbstractProvider.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/AbstractProvider.php on line 18 [15-Jul-2026 16:16:45 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ProviderRegistry.php on line 31 [24-Jul-2026 12:15:20 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/AbstractProvider.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/AbstractProvider.php on line 18 [24-Jul-2026 12:15:20 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ProviderRegistry.php on line 31 [25-Jul-2026 05:33:10 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ProviderRegistry.php on line 31 [25-Jul-2026 05:33:11 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/AbstractProvider.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/AbstractProvider.php on line 18 [04-Aug-2026 23:27:22 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ProviderRegistry.php on line 31 [04-Aug-2026 23:27:23 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/AbstractProvider.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/AbstractProvider.php on line 18 [13-Aug-2026 01:33:09 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/AbstractProvider.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/AbstractProvider.php on line 18 [13-Aug-2026 01:33:09 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ProviderRegistry.php on line 31 [13-Aug-2026 12:59:41 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/AbstractProvider.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/AbstractProvider.php on line 18 [13-Aug-2026 12:59:42 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ProviderRegistry.php on line 31 [19-Aug-2026 21:32:05 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/AbstractProvider.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/AbstractProvider.php on line 18 [19-Aug-2026 21:32:05 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ProviderRegistry.php on line 31 [19-Aug-2026 22:05:38 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ProviderRegistry.php on line 31 [19-Aug-2026 22:05:39 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/AbstractProvider.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/AbstractProvider.php on line 18 [19-Aug-2026 23:23:35 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ProviderRegistry.php on line 31 [19-Aug-2026 23:23:35 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/AbstractProvider.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/AbstractProvider.php on line 18 [03-Sep-2026 06:47:54 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/AbstractProvider.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/AbstractProvider.php on line 18 [03-Sep-2026 06:48:17 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ProviderRegistry.php on line 31 [03-Sep-2026 17:26:40 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/AbstractProvider.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/AbstractProvider.php on line 18 [03-Sep-2026 17:26:44 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ProviderRegistry.php on line 31 [04-Sep-2026 18:51:05 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/AbstractProvider.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/AbstractProvider.php on line 18 [05-Sep-2026 03:48:53 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ProviderRegistry.php on line 31 [10-Sep-2026 05:48:04 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/AbstractProvider.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/AbstractProvider.php on line 18 [10-Sep-2026 05:48:05 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ProviderRegistry.php on line 31 ���������������������������������������������������������������������������������������������Providers/OpenAiCompatibleImplementation/error_log��������������������������������������������������0000644�����������������00000047554�15250554011�0016534 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������[15-Jul-2026 16:16:49 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModelMetadataDirectory" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php:22 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php on line 22 [15-Jul-2026 16:16:49 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php:64 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php on line 64 [15-Jul-2026 16:16:49 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php:57 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php on line 57 [24-Jul-2026 12:15:34 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModelMetadataDirectory" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php:22 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php on line 22 [24-Jul-2026 12:15:34 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php:57 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php on line 57 [24-Jul-2026 12:15:34 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php:64 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php on line 64 [25-Jul-2026 05:33:33 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php:57 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php on line 57 [25-Jul-2026 05:33:34 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php:64 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php on line 64 [25-Jul-2026 05:33:34 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModelMetadataDirectory" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php:22 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php on line 22 [04-Aug-2026 23:27:51 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModelMetadataDirectory" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php:22 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php on line 22 [04-Aug-2026 23:27:51 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php:64 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php on line 64 [04-Aug-2026 23:27:51 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php:57 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php on line 57 [13-Aug-2026 01:33:26 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModelMetadataDirectory" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php:22 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php on line 22 [13-Aug-2026 01:33:27 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php:64 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php on line 64 [13-Aug-2026 01:33:27 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php:57 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php on line 57 [13-Aug-2026 12:59:56 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModelMetadataDirectory" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php:22 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php on line 22 [13-Aug-2026 12:59:56 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php:57 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php on line 57 [13-Aug-2026 12:59:56 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php:64 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php on line 64 [19-Aug-2026 21:32:05 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModelMetadataDirectory" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php:22 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php on line 22 [19-Aug-2026 21:32:05 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php:64 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php on line 64 [19-Aug-2026 21:32:05 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php:57 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php on line 57 [19-Aug-2026 22:05:43 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModelMetadataDirectory" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php:22 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php on line 22 [19-Aug-2026 22:05:43 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php:57 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php on line 57 [19-Aug-2026 22:05:44 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php:64 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php on line 64 [19-Aug-2026 23:23:36 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModelMetadataDirectory" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php:22 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php on line 22 [19-Aug-2026 23:23:37 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php:64 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php on line 64 [19-Aug-2026 23:23:37 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php:57 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php on line 57 [03-Sep-2026 06:29:35 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php:64 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php on line 64 [03-Sep-2026 06:31:19 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php:57 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php on line 57 [03-Sep-2026 06:32:49 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModelMetadataDirectory" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php:22 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php on line 22 [03-Sep-2026 18:00:53 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php:64 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php on line 64 [03-Sep-2026 18:00:53 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php:57 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php on line 57 [03-Sep-2026 18:00:53 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModelMetadataDirectory" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php:22 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php on line 22 [04-Sep-2026 21:07:52 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php:64 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php on line 64 [04-Sep-2026 21:07:57 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php:57 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php on line 57 [04-Sep-2026 21:08:02 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModelMetadataDirectory" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php:22 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php on line 22 [10-Sep-2026 11:34:21 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php:64 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php on line 64 [10-Sep-2026 11:34:21 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php:57 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php on line 57 [10-Sep-2026 11:34:22 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModelMetadataDirectory" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php:22 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php on line 22 ����������������������������������������������������������������������������������������������������������������������������������������������������Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php�����������0000644�����������������00000031733�15250554011�0026117 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\OpenAiCompatibleImplementation; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Common\Exception\RuntimeException; use WordPress\AiClient\Files\DTO\File; use WordPress\AiClient\Files\Enums\MediaOrientationEnum; use WordPress\AiClient\Messages\DTO\Message; use WordPress\AiClient\Messages\DTO\MessagePart; use WordPress\AiClient\Messages\Enums\MessageRoleEnum; use WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel; use WordPress\AiClient\Providers\Http\DTO\Request; use WordPress\AiClient\Providers\Http\DTO\Response; use WordPress\AiClient\Providers\Http\Enums\HttpMethodEnum; use WordPress\AiClient\Providers\Http\Exception\ResponseException; use WordPress\AiClient\Providers\Http\Util\ResponseUtil; use WordPress\AiClient\Providers\Models\ImageGeneration\Contracts\ImageGenerationModelInterface; use WordPress\AiClient\Results\DTO\Candidate; use WordPress\AiClient\Results\DTO\GenerativeAiResult; use WordPress\AiClient\Results\DTO\TokenUsage; use WordPress\AiClient\Results\Enums\FinishReasonEnum; /** * Base class for an image generation model for providers that implement OpenAI's API format. * * This abstract class is designed to work with any AI provider that offers an OpenAI-compatible * API endpoint for image generation, including but not limited to Anthropic, Google, and other * providers that have adopted OpenAI's image generation API specification as a standard interface. * * @since 0.1.0 * * @phpstan-type ImageGenerationParams array{ * model: string, * prompt: string, * n?: int, * response_format?: string, * output_format?: string|null, * size?: string, * ... * } * @phpstan-type ChoiceData array{ * url?: string, * b64_json?: string * } * @phpstan-type UsageData array{ * input_tokens?: int, * output_tokens?: int, * total_tokens?: int * } * @phpstan-type ResponseData array{ * id?: string, * data?: list<ChoiceData>, * usage?: UsageData * } */ abstract class AbstractOpenAiCompatibleImageGenerationModel extends AbstractApiBasedModel implements ImageGenerationModelInterface { /** * {@inheritDoc} * * @since 0.1.0 */ public function generateImageResult(array $prompt): GenerativeAiResult { $httpTransporter = $this->getHttpTransporter(); $params = $this->prepareGenerateImageParams($prompt); $request = $this->createRequest(HttpMethodEnum::POST(), 'images/generations', ['Content-Type' => 'application/json'], $params); // Add authentication credentials to the request. $request = $this->getRequestAuthentication()->authenticateRequest($request); // Send and process the request. $response = $httpTransporter->send($request); $this->throwIfNotSuccessful($response); return $this->parseResponseToGenerativeAiResult($response, isset($params['output_format']) && is_string($params['output_format']) ? "image/{$params['output_format']}" : 'image/png'); } /** * Prepares the given prompt and the model configuration into parameters for the API request. * * @since 0.1.0 * * @param list<Message> $prompt The prompt to generate an image for. Either a single message or a list of messages * from a chat. However as of today, OpenAI compatible image generation endpoints only * support a single user message. * @return ImageGenerationParams The parameters for the API request. */ protected function prepareGenerateImageParams(array $prompt): array { $config = $this->getConfig(); $params = ['model' => $this->metadata()->getId(), 'prompt' => $this->preparePromptParam($prompt)]; $candidateCount = $config->getCandidateCount(); if ($candidateCount !== null) { $params['n'] = $candidateCount; } $outputFileType = $config->getOutputFileType(); if ($outputFileType !== null) { $params['response_format'] = $outputFileType->isRemote() ? 'url' : 'b64_json'; } else { // The 'response_format' parameter is required, so we default to 'b64_json' if not set. $params['response_format'] = 'b64_json'; } $outputMimeType = $config->getOutputMimeType(); if ($outputMimeType !== null) { $params['output_format'] = preg_replace('/^image\//', '', $outputMimeType); } $outputMediaOrientation = $config->getOutputMediaOrientation(); $outputMediaAspectRatio = $config->getOutputMediaAspectRatio(); if ($outputMediaOrientation !== null || $outputMediaAspectRatio !== null) { $params['size'] = $this->prepareSizeParam($outputMediaOrientation, $outputMediaAspectRatio); } /* * Any custom options are added to the parameters as well. * This allows developers to pass other options that may be more niche or not yet supported by the SDK. */ $customOptions = $config->getCustomOptions(); foreach ($customOptions as $key => $value) { if (isset($params[$key])) { throw new InvalidArgumentException(sprintf('The custom option "%s" conflicts with an existing parameter.', $key)); } $params[$key] = $value; } /** @var ImageGenerationParams $params */ return $params; } /** * Prepares the prompt parameter for the API request. * * @since 0.1.0 * * @param list<Message> $messages The messages to prepare. However as of today, OpenAI compatible image generation * endpoints only support a single user message. * @return string The prepared prompt parameter. */ protected function preparePromptParam(array $messages): string { if (count($messages) !== 1) { throw new InvalidArgumentException('The API requires a single user message as prompt.'); } $message = $messages[0]; if (!$message->getRole()->isUser()) { throw new InvalidArgumentException('The API requires a user message as prompt.'); } $text = null; foreach ($message->getParts() as $part) { $text = $part->getText(); if ($text !== null) { break; } } if ($text === null) { throw new InvalidArgumentException('The API requires a single text message part as prompt.'); } return $text; } /** * Prepares the size parameter for the API request. * * @since 0.1.0 * * @param MediaOrientationEnum|null $orientation The desired media orientation. * @param string|null $aspectRatio The desired media aspect ratio. * @return string The prepared size parameter. */ protected function prepareSizeParam(?MediaOrientationEnum $orientation, ?string $aspectRatio): string { // Use aspect ratio if set, as it is more specific. if ($aspectRatio !== null) { switch ($aspectRatio) { case '1:1': return '1024x1024'; case '3:2': return '1536x1024'; case '7:4': return '1792x1024'; case '2:3': return '1024x1536'; case '4:7': return '1024x1792'; default: throw new InvalidArgumentException('The aspect ratio "' . $aspectRatio . '" is not supported.'); } } // This should always have a value, as the method is only called if at least one or the other is set. if ($orientation !== null) { if ($orientation->isLandscape()) { return '1536x1024'; } if ($orientation->isPortrait()) { return '1024x1536'; } } return '1024x1024'; } /** * Creates a request object for the provider's API. * * Implementations should use $this->getRequestOptions() to attach any * configured request options to the Request. * * @since 0.1.0 * * @param HttpMethodEnum $method The HTTP method. * @param string $path The API endpoint path, relative to the base URI. * @param array<string, string|list<string>> $headers The request headers. * @param string|array<string, mixed>|null $data The request data. * @return Request The request object. */ abstract protected function createRequest(HttpMethodEnum $method, string $path, array $headers = [], $data = null): Request; /** * Throws an exception if the response is not successful. * * @since 0.1.0 * * @param Response $response The HTTP response to check. * @throws ResponseException If the response is not successful. */ protected function throwIfNotSuccessful(Response $response): void { /* * While this method only calls the utility method, it's important to have it here as a protected method so * that child classes can override it if needed. */ ResponseUtil::throwIfNotSuccessful($response); } /** * Parses the response from the API endpoint to a generative AI result. * * @since 0.1.0 * * @param Response $response The response from the API endpoint. * @param string $expectedMimeType The expected MIME type the response is in. * @return GenerativeAiResult The parsed generative AI result. */ protected function parseResponseToGenerativeAiResult(Response $response, string $expectedMimeType = 'image/png'): GenerativeAiResult { /** @var ResponseData $responseData */ $responseData = $response->getData(); if (!isset($responseData['data']) || !$responseData['data']) { throw ResponseException::fromMissingData($this->providerMetadata()->getName(), 'data'); } if (!is_array($responseData['data'])) { throw ResponseException::fromInvalidData($this->providerMetadata()->getName(), 'data', 'The value must be an array.'); } $candidates = []; foreach ($responseData['data'] as $index => $choiceData) { if (!is_array($choiceData) || array_is_list($choiceData)) { throw ResponseException::fromInvalidData($this->providerMetadata()->getName(), "data[{$index}]", 'The value must be an associative array.'); } $candidates[] = $this->parseResponseChoiceToCandidate($choiceData, $index, $expectedMimeType); } $id = $this->getResultId($responseData); if (isset($responseData['usage']) && is_array($responseData['usage'])) { $usage = $responseData['usage']; $tokenUsage = new TokenUsage($usage['input_tokens'] ?? 0, $usage['output_tokens'] ?? 0, $usage['total_tokens'] ?? 0); } else { $tokenUsage = new TokenUsage(0, 0, 0); } // Use any other data from the response as provider-specific response metadata. $providerMetadata = $responseData; unset($providerMetadata['id'], $providerMetadata['data'], $providerMetadata['usage']); return new GenerativeAiResult($id, $candidates, $tokenUsage, $this->providerMetadata(), $this->metadata(), $providerMetadata); } /** * Parses a single choice from the API response into a Candidate object. * * @since 0.1.0 * * @param ChoiceData $choiceData The choice data from the API response. * @param int $index The index of the choice in the choices array. * @param string $expectedMimeType The expected MIME type the response is in. * @return Candidate The parsed candidate. * @throws RuntimeException If the choice data is invalid. */ protected function parseResponseChoiceToCandidate(array $choiceData, int $index, string $expectedMimeType = 'image/png'): Candidate { if (isset($choiceData['url']) && is_string($choiceData['url'])) { $imageFile = new File($choiceData['url'], $expectedMimeType); } elseif (isset($choiceData['b64_json']) && is_string($choiceData['b64_json'])) { $imageFile = new File($choiceData['b64_json'], $expectedMimeType); } else { throw ResponseException::fromInvalidData($this->providerMetadata()->getName(), "choices[{$index}]", 'The value must contain either a url or b64_json key with a string value.'); } $parts = [new MessagePart($imageFile)]; $message = new Message(MessageRoleEnum::model(), $parts); return new Candidate($message, FinishReasonEnum::stop()); } /** * Extracts the result ID from the API response data. * * @since 0.4.0 * * @param array<string, mixed> $responseData The response data from the API. * @return string The result ID. */ protected function getResultId(array $responseData): string { return isset($responseData['id']) && is_string($responseData['id']) ? $responseData['id'] : ''; } } �������������������������������������Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php���������0000644�����������������00000006373�15250554011�0026470 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\OpenAiCompatibleImplementation; use WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModelMetadataDirectory; use WordPress\AiClient\Providers\Http\DTO\Request; use WordPress\AiClient\Providers\Http\DTO\Response; use WordPress\AiClient\Providers\Http\Enums\HttpMethodEnum; use WordPress\AiClient\Providers\Http\Exception\ResponseException; use WordPress\AiClient\Providers\Http\Util\ResponseUtil; use WordPress\AiClient\Providers\Models\DTO\ModelMetadata; /** * Base class for a model metadata directory for providers that implement OpenAI's API format. * * This abstract class is designed to work with any AI provider that offers an OpenAI-compatible * models listing endpoint, including but not limited to Anthropic, Google, and other * providers that have adopted OpenAI's models API specification as a standard interface. * * @since 0.1.0 */ abstract class AbstractOpenAiCompatibleModelMetadataDirectory extends AbstractApiBasedModelMetadataDirectory { /** * {@inheritDoc} * * @since 0.1.0 */ protected function sendListModelsRequest(): array { $httpTransporter = $this->getHttpTransporter(); $request = $this->createRequest(HttpMethodEnum::GET(), 'models'); $request = $this->getRequestAuthentication()->authenticateRequest($request); $response = $httpTransporter->send($request); $this->throwIfNotSuccessful($response); $modelsMetadataList = $this->parseResponseToModelMetadataList($response); $modelMetadataMap = []; foreach ($modelsMetadataList as $modelMetadata) { $modelMetadataMap[$modelMetadata->getId()] = $modelMetadata; } return $modelMetadataMap; } /** * Creates a request object for the provider's API. * * @since 0.1.0 * * @param HttpMethodEnum $method The HTTP method. * @param string $path The API endpoint path, relative to the base URI. * @param array<string, string|list<string>> $headers The request headers. * @param string|array<string, mixed>|null $data The request data. * @return Request The request object. */ abstract protected function createRequest(HttpMethodEnum $method, string $path, array $headers = [], $data = null): Request; /** * Throws an exception if the response is not successful. * * @since 0.1.0 * * @param Response $response The HTTP response to check. * @throws ResponseException If the response is not successful. */ protected function throwIfNotSuccessful(Response $response): void { /* * While this method only calls the utility method, it's important to have it here as a protected method so * that child classes can override it if needed. */ ResponseUtil::throwIfNotSuccessful($response); } /** * Parses the response from the API endpoint to list models into a list of model metadata objects. * * @since 0.1.0 * * @param Response $response The response from the API endpoint to list models. * @return list<ModelMetadata> List of model metadata objects. */ abstract protected function parseResponseToModelMetadataList(Response $response): array; } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php������������0000644�����������������00000061237�15250554011�0026023 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\OpenAiCompatibleImplementation; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Common\Exception\RuntimeException; use WordPress\AiClient\Messages\DTO\Message; use WordPress\AiClient\Messages\DTO\MessagePart; use WordPress\AiClient\Messages\Enums\MessagePartChannelEnum; use WordPress\AiClient\Messages\Enums\MessageRoleEnum; use WordPress\AiClient\Messages\Enums\ModalityEnum; use WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel; use WordPress\AiClient\Providers\Http\DTO\Request; use WordPress\AiClient\Providers\Http\DTO\Response; use WordPress\AiClient\Providers\Http\Enums\HttpMethodEnum; use WordPress\AiClient\Providers\Http\Exception\ResponseException; use WordPress\AiClient\Providers\Http\Util\ResponseUtil; use WordPress\AiClient\Providers\Models\TextGeneration\Contracts\TextGenerationModelInterface; use WordPress\AiClient\Results\DTO\Candidate; use WordPress\AiClient\Results\DTO\GenerativeAiResult; use WordPress\AiClient\Results\DTO\TokenUsage; use WordPress\AiClient\Results\Enums\FinishReasonEnum; use WordPress\AiClient\Tools\DTO\FunctionCall; use WordPress\AiClient\Tools\DTO\FunctionDeclaration; /** * Base class for a text generation model for providers that implement OpenAI's API format. * * This abstract class is designed to work with any AI provider that offers an OpenAI-compatible * API endpoint, including but not limited to Anthropic, Google, and other providers * that have adopted OpenAI's API specification as a standard interface. * * @since 0.1.0 * * @phpstan-type ToolCallData array{ * type?: string, * id?: string, * function?: array{ * name?: string, * arguments: string|array<string, mixed> * } * } * @phpstan-type MessageData array{ * role?: string, * reasoning_content?: string, * content?: string, * tool_calls?: list<ToolCallData> * } * @phpstan-type ChoiceData array{ * message?: MessageData, * finish_reason?: string * } * @phpstan-type UsageData array{ * prompt_tokens?: int, * completion_tokens?: int, * total_tokens?: int * } * @phpstan-type ResponseData array{ * id?: string, * choices?: list<ChoiceData>, * usage?: UsageData * } */ abstract class AbstractOpenAiCompatibleTextGenerationModel extends AbstractApiBasedModel implements TextGenerationModelInterface { /** * {@inheritDoc} * * @since 0.1.0 */ final public function generateTextResult(array $prompt): GenerativeAiResult { $httpTransporter = $this->getHttpTransporter(); $params = $this->prepareGenerateTextParams($prompt); $request = $this->createRequest(HttpMethodEnum::POST(), 'chat/completions', ['Content-Type' => 'application/json'], $params); // Add authentication credentials to the request. $request = $this->getRequestAuthentication()->authenticateRequest($request); // Send and process the request. $response = $httpTransporter->send($request); $this->throwIfNotSuccessful($response); return $this->parseResponseToGenerativeAiResult($response); } /** * Prepares the given prompt and the model configuration into parameters for the API request. * * @since 0.1.0 * * @param list<Message> $prompt The prompt to generate text for. Either a single message or a list of messages * from a chat. * @return array<string, mixed> The parameters for the API request. */ protected function prepareGenerateTextParams(array $prompt): array { $config = $this->getConfig(); $params = ['model' => $this->metadata()->getId(), 'messages' => $this->prepareMessagesParam($prompt, $config->getSystemInstruction())]; $outputModalities = $config->getOutputModalities(); if (is_array($outputModalities)) { $this->validateOutputModalities($outputModalities); if (count($outputModalities) > 1) { $params['modalities'] = $this->prepareOutputModalitiesParam($outputModalities); } } $candidateCount = $config->getCandidateCount(); if ($candidateCount !== null) { $params['n'] = $candidateCount; } $maxTokens = $config->getMaxTokens(); if ($maxTokens !== null) { $params['max_tokens'] = $maxTokens; } $temperature = $config->getTemperature(); if ($temperature !== null) { $params['temperature'] = $temperature; } $topP = $config->getTopP(); if ($topP !== null) { $params['top_p'] = $topP; } $stopSequences = $config->getStopSequences(); if (is_array($stopSequences)) { $params['stop'] = $stopSequences; } $presencePenalty = $config->getPresencePenalty(); if ($presencePenalty !== null) { $params['presence_penalty'] = $presencePenalty; } $frequencyPenalty = $config->getFrequencyPenalty(); if ($frequencyPenalty !== null) { $params['frequency_penalty'] = $frequencyPenalty; } $logprobs = $config->getLogprobs(); if ($logprobs !== null) { $params['logprobs'] = $logprobs; } $topLogprobs = $config->getTopLogprobs(); if ($topLogprobs !== null) { $params['top_logprobs'] = $topLogprobs; } $functionDeclarations = $config->getFunctionDeclarations(); if (is_array($functionDeclarations)) { $params['tools'] = $this->prepareToolsParam($functionDeclarations); } $outputMimeType = $config->getOutputMimeType(); if ('application/json' === $outputMimeType) { $outputSchema = $config->getOutputSchema(); $params['response_format'] = $this->prepareResponseFormatParam($outputSchema); } /* * Any custom options are added to the parameters as well. * This allows developers to pass other options that may be more niche or not yet supported by the SDK. */ $customOptions = $config->getCustomOptions(); foreach ($customOptions as $key => $value) { if (isset($params[$key])) { throw new InvalidArgumentException(sprintf('The custom option "%s" conflicts with an existing parameter.', $key)); } $params[$key] = $value; } return $params; } /** * Prepares the messages parameter for the API request. * * @since 0.1.0 * * @param list<Message> $messages The messages to prepare. * @param string|null $systemInstruction An optional system instruction to prepend to the messages. * @return list<array<string, mixed>> The prepared messages parameter. */ protected function prepareMessagesParam(array $messages, ?string $systemInstruction = null): array { $messagesParam = array_map(function (Message $message): array { // Special case: Function response. $messageParts = $message->getParts(); if (count($messageParts) === 1 && $messageParts[0]->getType()->isFunctionResponse()) { $functionResponse = $messageParts[0]->getFunctionResponse(); if (!$functionResponse) { // This should be impossible due to class internals, but still needs to be checked. throw new RuntimeException('The function response typed message part must contain a function response.'); } return ['role' => 'tool', 'content' => json_encode($functionResponse->getResponse()), 'tool_call_id' => $functionResponse->getId()]; } $messageData = ['role' => $this->getMessageRoleString($message->getRole()), 'content' => array_values(array_filter(array_map([$this, 'getMessagePartContentData'], $messageParts)))]; // Only include tool_calls if there are any (OpenAI rejects empty arrays). $toolCalls = array_values(array_filter(array_map([$this, 'getMessagePartToolCallData'], $messageParts))); if (!empty($toolCalls)) { $messageData['tool_calls'] = $toolCalls; } return $messageData; }, $messages); if ($systemInstruction) { array_unshift($messagesParam, [ /* * TODO: Replace this with 'developer' in the future. * See https://platform.openai.com/docs/api-reference/chat/create#chat_create-messages */ 'role' => 'system', 'content' => [['type' => 'text', 'text' => $systemInstruction]], ]); } return $messagesParam; } /** * Returns the OpenAI API specific role string for the given message role. * * @since 0.1.0 * * @param MessageRoleEnum $role The message role. * @return string The role for the API request. */ protected function getMessageRoleString(MessageRoleEnum $role): string { if ($role === MessageRoleEnum::model()) { return 'assistant'; } return 'user'; } /** * Returns the OpenAI API specific content data for a message part. * * @since 0.1.0 * * @param MessagePart $part The message part to get the data for. * @return ?array<string, mixed> The data for the message content part, or null if not applicable. * @throws InvalidArgumentException If the message part type or data is unsupported. */ protected function getMessagePartContentData(MessagePart $part): ?array { $type = $part->getType(); if ($type->isText()) { /* * The OpenAI Chat Completions API spec does not support annotating thought parts as input, * so we instead skip them. */ if ($part->getChannel()->isThought()) { return null; } return ['type' => 'text', 'text' => $part->getText()]; } if ($type->isFile()) { $file = $part->getFile(); if (!$file) { // This should be impossible due to class internals, but still needs to be checked. throw new RuntimeException('The file typed message part must contain a file.'); } if ($file->isRemote()) { if ($file->isImage()) { return ['type' => 'image_url', 'image_url' => ['url' => $file->getUrl()]]; } throw new InvalidArgumentException(sprintf('Unsupported MIME type "%s" for remote file message part.', $file->getMimeType())); } // Else, it is an inline file. if ($file->isImage()) { return ['type' => 'image_url', 'image_url' => ['url' => $file->getDataUri()]]; } if ($file->isAudio()) { return ['type' => 'input_audio', 'input_audio' => ['data' => $file->getBase64Data(), 'format' => $file->getMimeTypeObject()->toExtension()]]; } throw new InvalidArgumentException(sprintf('Unsupported MIME type "%s" for inline file message part.', $file->getMimeType())); } if ($type->isFunctionCall()) { // Skip, as this is separately included. See `getMessagePartToolCallData()`. return null; } if ($type->isFunctionResponse()) { // Special case: Function response. throw new InvalidArgumentException('The API only allows a single function response, as the only content of the message.'); } throw new InvalidArgumentException(sprintf('Unsupported message part type "%s".', $type)); } /** * Returns the OpenAI API specific tool calls data for a message part. * * @since 0.1.0 * * @param MessagePart $part The message part to get the data for. * @return ?array<string, mixed> The data for the message tool call part, or null if not applicable. * @throws InvalidArgumentException If the message part type or data is unsupported. */ protected function getMessagePartToolCallData(MessagePart $part): ?array { $type = $part->getType(); if ($type->isFunctionCall()) { $functionCall = $part->getFunctionCall(); if (!$functionCall) { // This should be impossible due to class internals, but still needs to be checked. throw new RuntimeException('The function call typed message part must contain a function call.'); } $args = $functionCall->getArgs(); /* * Ensure null or empty arrays become empty objects for JSON encoding. * While in theory the JSON schema could also dictate a type of * 'array', in practice function arguments are typically of type * 'object'. More importantly, the OpenAI API specification seems * to expect that, and does not support passing arrays as the root * value. The null check handles the case where FunctionCall normalizes * empty arrays to null. */ if ($args === null || is_array($args) && count($args) === 0) { $args = new \stdClass(); } return ['type' => 'function', 'id' => $functionCall->getId(), 'function' => ['name' => $functionCall->getName(), 'arguments' => json_encode($args)]]; } // All other types are handled in `getMessagePartContentData()`. return null; } /** * Validates that the given output modalities to ensure that at least one output modality is text. * * @since 0.1.0 * * @param array<ModalityEnum> $outputModalities The output modalities to validate. * @throws InvalidArgumentException If no text output modality is present. */ protected function validateOutputModalities(array $outputModalities): void { // If no output modalities are set, it's fine, as we can assume text. if (count($outputModalities) === 0) { return; } foreach ($outputModalities as $modality) { if ($modality->isText()) { return; } } throw new InvalidArgumentException('A text output modality must be present when generating text.'); } /** * Prepares the output modalities parameter for the API request. * * @since 0.1.0 * * @param array<ModalityEnum> $modalities The modalities to prepare. * @return list<string> The prepared modalities parameter. */ protected function prepareOutputModalitiesParam(array $modalities): array { $prepared = []; foreach ($modalities as $modality) { if ($modality->isText()) { $prepared[] = 'text'; } elseif ($modality->isImage()) { $prepared[] = 'image'; } elseif ($modality->isAudio()) { $prepared[] = 'audio'; } else { throw new InvalidArgumentException(sprintf('Unsupported output modality "%s".', $modality)); } } return $prepared; } /** * Prepares the tools parameter for the API request. * * @since 0.1.0 * * @param list<FunctionDeclaration> $functionDeclarations The function declarations. * @return list<array<string, mixed>> The prepared tools parameter. */ protected function prepareToolsParam(array $functionDeclarations): array { $tools = []; foreach ($functionDeclarations as $functionDeclaration) { $tools[] = ['type' => 'function', 'function' => $functionDeclaration->toArray()]; } return $tools; } /** * Prepares the response format parameter for the API request. * * This is only called if the output MIME type is `application/json`. * * @since 0.1.0 * * @param array<string, mixed>|null $outputSchema The output schema. * @return array<string, mixed> The prepared response format parameter. */ protected function prepareResponseFormatParam(?array $outputSchema): array { if (is_array($outputSchema)) { return ['type' => 'json_schema', 'json_schema' => $outputSchema]; } return ['type' => 'json_object']; } /** * Creates a request object for the provider's API. * * Implementations should use $this->getRequestOptions() to attach any * configured request options to the Request. * * @since 0.1.0 * * @param HttpMethodEnum $method The HTTP method. * @param string $path The API endpoint path, relative to the base URI. * @param array<string, string|list<string>> $headers The request headers. * @param string|array<string, mixed>|null $data The request data. * @return Request The request object. */ abstract protected function createRequest(HttpMethodEnum $method, string $path, array $headers = [], $data = null): Request; /** * Throws an exception if the response is not successful. * * @since 0.1.0 * * @param Response $response The HTTP response to check. * @throws ResponseException If the response is not successful. */ protected function throwIfNotSuccessful(Response $response): void { /* * While this method only calls the utility method, it's important to have it here as a protected method so * that child classes can override it if needed. */ ResponseUtil::throwIfNotSuccessful($response); } /** * Parses the response from the API endpoint to a generative AI result. * * @since 0.1.0 * * @param Response $response The response from the API endpoint. * @return GenerativeAiResult The parsed generative AI result. */ protected function parseResponseToGenerativeAiResult(Response $response): GenerativeAiResult { /** @var ResponseData $responseData */ $responseData = $response->getData(); if (!isset($responseData['choices']) || !$responseData['choices']) { throw ResponseException::fromMissingData($this->providerMetadata()->getName(), 'choices'); } if (!is_array($responseData['choices'])) { throw ResponseException::fromInvalidData($this->providerMetadata()->getName(), 'choices', 'The value must be an array.'); } $candidates = []; foreach ($responseData['choices'] as $index => $choiceData) { if (!is_array($choiceData) || array_is_list($choiceData)) { throw ResponseException::fromInvalidData($this->providerMetadata()->getName(), "choices[{$index}]", 'The value must be an associative array.'); } $candidates[] = $this->parseResponseChoiceToCandidate($choiceData, $index); } $id = isset($responseData['id']) && is_string($responseData['id']) ? $responseData['id'] : ''; if (isset($responseData['usage']) && is_array($responseData['usage'])) { $usage = $responseData['usage']; $tokenUsage = new TokenUsage($usage['prompt_tokens'] ?? 0, $usage['completion_tokens'] ?? 0, $usage['total_tokens'] ?? 0); } else { $tokenUsage = new TokenUsage(0, 0, 0); } // Use any other data from the response as provider-specific response metadata. $additionalData = $responseData; unset($additionalData['id'], $additionalData['choices'], $additionalData['usage']); return new GenerativeAiResult($id, $candidates, $tokenUsage, $this->providerMetadata(), $this->metadata(), $additionalData); } /** * Parses a single choice from the API response into a Candidate object. * * @since 0.1.0 * * @param ChoiceData $choiceData The choice data from the API response. * @param int $index The index of the choice in the choices array. * @return Candidate The parsed candidate. * @throws RuntimeException If the choice data is invalid. */ protected function parseResponseChoiceToCandidate(array $choiceData, int $index): Candidate { if (!isset($choiceData['message']) || !is_array($choiceData['message']) || array_is_list($choiceData['message'])) { throw ResponseException::fromMissingData($this->providerMetadata()->getName(), "choices[{$index}].message"); } if (!isset($choiceData['finish_reason']) || !is_string($choiceData['finish_reason'])) { throw ResponseException::fromMissingData($this->providerMetadata()->getName(), "choices[{$index}].finish_reason"); } $messageData = $choiceData['message']; $message = $this->parseResponseChoiceMessage($messageData, $index); switch ($choiceData['finish_reason']) { case 'stop': $finishReason = FinishReasonEnum::stop(); break; case 'length': $finishReason = FinishReasonEnum::length(); break; case 'content_filter': $finishReason = FinishReasonEnum::contentFilter(); break; case 'tool_calls': $finishReason = FinishReasonEnum::toolCalls(); break; default: throw ResponseException::fromInvalidData($this->providerMetadata()->getName(), "choices[{$index}].finish_reason", sprintf('Invalid finish reason "%s".', $choiceData['finish_reason'])); } return new Candidate($message, $finishReason); } /** * Parses the message from a choice in the API response. * * @since 0.1.0 * * @param MessageData $messageData The message data from the API response. * @param int $index The index of the choice in the choices array. * @return Message The parsed message. */ protected function parseResponseChoiceMessage(array $messageData, int $index): Message { $role = isset($messageData['role']) && 'user' === $messageData['role'] ? MessageRoleEnum::user() : MessageRoleEnum::model(); $parts = $this->parseResponseChoiceMessageParts($messageData, $index); return new Message($role, $parts); } /** * Parses the message parts from a choice in the API response. * * @since 0.1.0 * * @param MessageData $messageData The message data from the API response. * @param int $index The index of the choice in the choices array. * @return MessagePart[] The parsed message parts. */ protected function parseResponseChoiceMessageParts(array $messageData, int $index): array { $parts = []; if (isset($messageData['reasoning_content']) && is_string($messageData['reasoning_content'])) { $parts[] = new MessagePart($messageData['reasoning_content'], MessagePartChannelEnum::thought()); } if (isset($messageData['content']) && is_string($messageData['content'])) { $parts[] = new MessagePart($messageData['content']); } if (isset($messageData['tool_calls']) && is_array($messageData['tool_calls'])) { foreach ($messageData['tool_calls'] as $toolCallIndex => $toolCallData) { $toolCallPart = $this->parseResponseChoiceMessageToolCallPart($toolCallData); if (!$toolCallPart) { throw ResponseException::fromInvalidData($this->providerMetadata()->getName(), "choices[{$index}].message.tool_calls[{$toolCallIndex}]", 'The response includes a tool call of an unexpected type.'); } $parts[] = $toolCallPart; } } return $parts; } /** * Parses a tool call part from the API response. * * @since 0.1.0 * * @param ToolCallData $toolCallData The tool call data from the API response. * @return MessagePart|null The parsed message part for the tool call, or null if not applicable. */ protected function parseResponseChoiceMessageToolCallPart(array $toolCallData): ?MessagePart { /* * For now, only function calls are supported. * * Not all OpenAI compatible APIs include a 'type' key, so we only check its value if it is set. */ if (isset($toolCallData['type']) && 'function' !== $toolCallData['type'] || !isset($toolCallData['function']) || !is_array($toolCallData['function'])) { return null; } $functionArguments = is_string($toolCallData['function']['arguments']) ? json_decode($toolCallData['function']['arguments'], \true) : $toolCallData['function']['arguments']; $functionCall = new FunctionCall(isset($toolCallData['id']) && is_string($toolCallData['id']) ? $toolCallData['id'] : null, isset($toolCallData['function']['name']) && is_string($toolCallData['function']['name']) ? $toolCallData['function']['name'] : null, $functionArguments); return new MessagePart($functionCall); } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Enums/error_log���������������������������������������������������������������������������0000644�����������������00000021260�15250554011�0011524 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������[15-Jul-2026 16:16:46 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php on line 19 [15-Jul-2026 16:16:46 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php on line 17 [24-Jul-2026 12:15:22 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php on line 17 [24-Jul-2026 12:15:22 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php on line 19 [25-Jul-2026 05:33:14 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php on line 19 [25-Jul-2026 05:33:14 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php on line 17 [04-Aug-2026 23:27:49 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php on line 17 [04-Aug-2026 23:27:50 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php on line 19 [13-Aug-2026 01:33:15 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php on line 17 [13-Aug-2026 01:33:16 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php on line 19 [13-Aug-2026 13:00:03 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php on line 19 [13-Aug-2026 13:00:03 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php on line 17 [19-Aug-2026 21:32:05 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php on line 17 [19-Aug-2026 21:32:05 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php on line 19 [19-Aug-2026 22:06:11 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php on line 19 [19-Aug-2026 22:06:11 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php on line 17 [19-Aug-2026 23:24:30 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php on line 17 [19-Aug-2026 23:24:30 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php on line 19 [03-Sep-2026 06:47:53 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php on line 17 [03-Sep-2026 06:51:08 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php on line 19 [04-Sep-2026 00:27:07 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php on line 19 [04-Sep-2026 07:42:52 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php on line 17 [05-Sep-2026 01:58:41 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php on line 19 [05-Sep-2026 09:42:33 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php:17 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php on line 17 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/Enums/ProviderTypeEnum.php����������������������������������������������������������������0000644�����������������00000001673�15250554011�0013607 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Enums; use WordPress\AiClient\Common\AbstractEnum; /** * Enum for provider types. * * @since 0.1.0 * * @method static self cloud() Creates an instance for CLOUD type. * @method static self server() Creates an instance for SERVER type. * @method static self client() Creates an instance for CLIENT type. * @method bool isCloud() Checks if the type is CLOUD. * @method bool isServer() Checks if the type is SERVER. * @method bool isClient() Checks if the type is CLIENT. */ class ProviderTypeEnum extends AbstractEnum { /** * Cloud-based AI provider (e.g. models available via external REST APIs). */ public const CLOUD = 'cloud'; /** * Server-side AI provider (e.g. self-hosted models). */ public const SERVER = 'server'; /** * Client-side AI provider (e.g. browser-based models). */ public const CLIENT = 'client'; } ���������������������������������������������������������������������Providers/Enums/ToolTypeEnum.php��������������������������������������������������������������������0000644�����������������00000001365�15250554011�0012730 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Enums; use WordPress\AiClient\Common\AbstractEnum; /** * Enum for tool types. * * @since 0.1.0 * * @method static self functionDeclarations() Creates an instance for FUNCTION_DECLARATIONS type. * @method static self webSearch() Creates an instance for WEB_SEARCH type. * @method bool isFunctionDeclarations() Checks if the type is FUNCTION_DECLARATIONS. * @method bool isWebSearch() Checks if the type is WEB_SEARCH. */ class ToolTypeEnum extends AbstractEnum { /** * Function declarations tool type. */ public const FUNCTION_DECLARATIONS = 'function_declarations'; /** * Web search tool type. */ public const WEB_SEARCH = 'web_search'; } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/AbstractProvider.php����������������������������������������������������������������������0000644�����������������00000010014�15250554011�0012502 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers; use WordPress\AiClient\Providers\Contracts\ModelMetadataDirectoryInterface; use WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface; use WordPress\AiClient\Providers\Contracts\ProviderInterface; use WordPress\AiClient\Providers\DTO\ProviderMetadata; use WordPress\AiClient\Providers\Models\Contracts\ModelInterface; use WordPress\AiClient\Providers\Models\DTO\ModelConfig; use WordPress\AiClient\Providers\Models\DTO\ModelMetadata; /** * Base class for a provider. * * @since 0.1.0 */ abstract class AbstractProvider implements ProviderInterface { /** * @var array<string, ProviderMetadata> Cache for provider metadata per class. */ private static array $metadataCache = []; /** * @var array<string, ProviderAvailabilityInterface> Cache for provider availability per class. */ private static array $availabilityCache = []; /** * @var array<string, ModelMetadataDirectoryInterface> Cache for model metadata directory per class. */ private static array $modelMetadataDirectoryCache = []; /** * {@inheritDoc} * * @since 0.1.0 */ final public static function metadata(): ProviderMetadata { $className = static::class; if (!isset(self::$metadataCache[$className])) { self::$metadataCache[$className] = static::createProviderMetadata(); } return self::$metadataCache[$className]; } /** * {@inheritDoc} * * @since 0.1.0 */ final public static function model(string $modelId, ?ModelConfig $modelConfig = null): ModelInterface { $providerMetadata = static::metadata(); $modelMetadata = static::modelMetadataDirectory()->getModelMetadata($modelId); $model = static::createModel($modelMetadata, $providerMetadata); if ($modelConfig) { $model->setConfig($modelConfig); } return $model; } /** * {@inheritDoc} * * @since 0.1.0 */ final public static function availability(): ProviderAvailabilityInterface { $className = static::class; if (!isset(self::$availabilityCache[$className])) { self::$availabilityCache[$className] = static::createProviderAvailability(); } return self::$availabilityCache[$className]; } /** * {@inheritDoc} * * @since 0.1.0 */ final public static function modelMetadataDirectory(): ModelMetadataDirectoryInterface { $className = static::class; if (!isset(self::$modelMetadataDirectoryCache[$className])) { self::$modelMetadataDirectoryCache[$className] = static::createModelMetadataDirectory(); } return self::$modelMetadataDirectoryCache[$className]; } /** * Creates a model instance based on the given model metadata and provider metadata. * * @since 0.1.0 * * @param ModelMetadata $modelMetadata The model metadata. * @param ProviderMetadata $providerMetadata The provider metadata. * @return ModelInterface The new model instance. */ abstract protected static function createModel(ModelMetadata $modelMetadata, ProviderMetadata $providerMetadata): ModelInterface; /** * Creates the provider metadata instance. * * @since 0.1.0 * * @return ProviderMetadata The provider metadata. */ abstract protected static function createProviderMetadata(): ProviderMetadata; /** * Creates the provider availability instance. * * @since 0.1.0 * * @return ProviderAvailabilityInterface The provider availability. */ abstract protected static function createProviderAvailability(): ProviderAvailabilityInterface; /** * Creates the model metadata directory instance. * * @since 0.1.0 * * @return ModelMetadataDirectoryInterface The model metadata directory. */ abstract protected static function createModelMetadataDirectory(): ModelMetadataDirectoryInterface; } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/ProviderRegistry.php����������������������������������������������������������������������0000644�����������������00000057020�15250554011�0012557 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers; use WordPress\AiClientDependencies\Http\Discovery\Exception\NotFoundException as DiscoveryNotFoundException; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Common\Exception\RuntimeException; use WordPress\AiClient\Providers\Contracts\ProviderInterface; use WordPress\AiClient\Providers\Contracts\ProviderWithOperationsHandlerInterface; use WordPress\AiClient\Providers\DTO\ProviderMetadata; use WordPress\AiClient\Providers\DTO\ProviderModelsMetadata; use WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface; use WordPress\AiClient\Providers\Http\Contracts\RequestAuthenticationInterface; use WordPress\AiClient\Providers\Http\Contracts\WithHttpTransporterInterface; use WordPress\AiClient\Providers\Http\Contracts\WithRequestAuthenticationInterface; use WordPress\AiClient\Providers\Http\HttpTransporterFactory; use WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait; use WordPress\AiClient\Providers\Models\Contracts\ModelInterface; use WordPress\AiClient\Providers\Models\DTO\ModelConfig; use WordPress\AiClient\Providers\Models\DTO\ModelMetadata; use WordPress\AiClient\Providers\Models\DTO\ModelRequirements; /** * Registry for managing AI providers and their models. * * This class provides a centralized way to register AI providers, discover * their capabilities, and find suitable models based on requirements. * * @since 0.1.0 */ class ProviderRegistry implements WithHttpTransporterInterface { use WithHttpTransporterTrait { setHttpTransporter as setHttpTransporterOriginal; } /** * @var array<string, class-string<ProviderInterface>> Mapping of provider IDs to class names. */ private array $registeredIdsToClassNames = []; /** * @var array<class-string<ProviderInterface>, string> Mapping of provider class names to IDs. */ private array $registeredClassNamesToIds = []; /** * @var array<class-string<ProviderInterface>, RequestAuthenticationInterface> Mapping of provider class names to * authentication instances. */ private array $providerAuthenticationInstances = []; /** * Registers a provider class with the registry. * * @since 0.1.0 * * @param class-string<ProviderInterface> $className The fully qualified provider class name implementing the * ProviderInterface * @throws InvalidArgumentException If the class doesn't exist or implement the required interface. */ public function registerProvider(string $className): void { if (!class_exists($className)) { throw new InvalidArgumentException(sprintf('Provider class does not exist: %s', $className)); } // Validate that class implements ProviderInterface if (!is_subclass_of($className, ProviderInterface::class)) { throw new InvalidArgumentException(sprintf('Provider class must implement %s: %s', ProviderInterface::class, $className)); } $metadata = $className::metadata(); if (!$metadata instanceof ProviderMetadata) { throw new InvalidArgumentException(sprintf('Provider must return ProviderMetadata from metadata() method: %s', $className)); } // If there is already a HTTP transporter instance set, hook it up to the provider as needed. try { $httpTransporter = $this->getHttpTransporter(); } catch (RuntimeException $e) { /* * If this fails, it's okay. There is no defined sequence between setting the HTTP transporter in the * registry and registering providers in it, so it might be that the transporter is set later. It will be * hooked up then. * But for now we can ignore this exception and attempt to set the default HTTP transporter, if possible. */ try { $this->setHttpTransporter(HttpTransporterFactory::createTransporter()); $httpTransporter = $this->getHttpTransporter(); } catch (DiscoveryNotFoundException $e) { /* * If no HTTP client implementation can be discovered yet, we can ignore this for now. * It might be set later, so it's not a hard error at this point. * We'll try again the next time a provider is registered, or maybe by that time an explicit * HTTP transporter will have been set. */ } } if (isset($httpTransporter)) { $this->setHttpTransporterForProvider($className, $httpTransporter); } // Hook up the request authentication instance, using a default if not set. if (!isset($this->providerAuthenticationInstances[$className])) { $defaultProviderAuthentication = $this->createDefaultProviderRequestAuthentication($className); if ($defaultProviderAuthentication !== null) { $this->providerAuthenticationInstances[$className] = $defaultProviderAuthentication; } } if (isset($this->providerAuthenticationInstances[$className])) { $this->setRequestAuthenticationForProvider($className, $this->providerAuthenticationInstances[$className]); } $this->registeredIdsToClassNames[$metadata->getId()] = $className; $this->registeredClassNamesToIds[$className] = $metadata->getId(); } /** * Gets a list of all registered provider IDs. * * @since 0.1.0 * * @return list<string> List of registered provider IDs. */ public function getRegisteredProviderIds(): array { return array_keys($this->registeredIdsToClassNames); } /** * Checks if a provider is registered. * * @since 0.1.0 * * @param string|class-string<ProviderInterface> $idOrClassName The provider ID or class name to check. * @return bool True if the provider is registered. */ public function hasProvider(string $idOrClassName): bool { return $this->isRegisteredId($idOrClassName) || $this->isRegisteredClassName($idOrClassName); } /** * Gets the class name for a registered provider. * * @since 0.1.0 * * @param string|class-string<ProviderInterface> $idOrClassName The provider ID or class name. * @return class-string<ProviderInterface> The provider class name. * @throws InvalidArgumentException If the provider is not registered. */ public function getProviderClassName(string $idOrClassName): string { // If it's already a class name, return it if ($this->isRegisteredClassName($idOrClassName)) { return $idOrClassName; } // If it's a registered ID, return its class name if ($this->isRegisteredId($idOrClassName)) { return $this->registeredIdsToClassNames[$idOrClassName]; } // Not found throw new InvalidArgumentException(sprintf('Provider not registered: %s', $idOrClassName)); } /** * Gets the provider ID for a registered provider. * * @since 0.2.0 * * @param string|class-string<ProviderInterface> $idOrClassName The provider ID or class name. * @return string The provider ID. * @throws InvalidArgumentException If the provider is not registered. */ public function getProviderId(string $idOrClassName): string { // If it's already an ID, return it if ($this->isRegisteredId($idOrClassName)) { return $idOrClassName; } // If it's a registered class name, return its ID if ($this->isRegisteredClassName($idOrClassName)) { return $this->registeredClassNamesToIds[$idOrClassName]; } // Not found throw new InvalidArgumentException(sprintf('Provider not registered: %s', $idOrClassName)); } /** * Checks if a provider is properly configured. * * @since 0.1.0 * * @param string|class-string<ProviderInterface> $idOrClassName The provider ID or class name. * @return bool True if the provider is configured and ready to use. */ public function isProviderConfigured(string $idOrClassName): bool { try { $className = $this->resolveProviderClassName($idOrClassName); // Use static method from ProviderInterface /** @var class-string<ProviderInterface> $className */ $availability = $className::availability(); return $availability->isConfigured(); } catch (InvalidArgumentException $e) { return \false; } } /** * Finds models across all available providers that support the given requirements. * * @since 0.1.0 * * @param ModelRequirements $modelRequirements The requirements to match against. * @return list<ProviderModelsMetadata> List of provider models metadata that match requirements. */ public function findModelsMetadataForSupport(ModelRequirements $modelRequirements): array { $results = []; foreach ($this->registeredIdsToClassNames as $providerId => $className) { $providerResults = $this->findProviderModelsMetadataForSupport($providerId, $modelRequirements); if (!empty($providerResults)) { // Use static method from ProviderInterface /** @var class-string<ProviderInterface> $className */ $providerMetadata = $className::metadata(); $results[] = new ProviderModelsMetadata($providerMetadata, $providerResults); } } return $results; } /** * Finds models within a specific available provider that support the given requirements. * * @since 0.1.0 * * @param string $idOrClassName The provider ID or class name. * @param ModelRequirements $modelRequirements The requirements to match against. * @return list<ModelMetadata> List of model metadata that match requirements. */ public function findProviderModelsMetadataForSupport(string $idOrClassName, ModelRequirements $modelRequirements): array { $className = $this->resolveProviderClassName($idOrClassName); // If the provider is not configured, there is no way to use it, so it is considered unavailable. if (!$this->isProviderConfigured($className)) { return []; } $modelMetadataDirectory = $className::modelMetadataDirectory(); // Filter models that meet requirements $matchingModels = []; foreach ($modelMetadataDirectory->listModelMetadata() as $modelMetadata) { if ($modelRequirements->areMetBy($modelMetadata)) { $matchingModels[] = $modelMetadata; } } return $matchingModels; } /** * Gets a configured model instance from a provider. * * @since 0.1.0 * * @param string|class-string<ProviderInterface> $idOrClassName The provider ID or class name. * @param string $modelId The model identifier. * @param ModelConfig|null $modelConfig The model configuration. * @return ModelInterface The configured model instance. * @throws InvalidArgumentException If provider or model is not found. */ public function getProviderModel(string $idOrClassName, string $modelId, ?ModelConfig $modelConfig = null): ModelInterface { $className = $this->resolveProviderClassName($idOrClassName); $modelInstance = $className::model($modelId, $modelConfig); $this->bindModelDependencies($modelInstance); return $modelInstance; } /** * Binds dependencies to a model instance. * * This method injects required dependencies such as HTTP transporter * and authentication into model instances that need them. * * @since 0.1.0 * * @param ModelInterface $modelInstance The model instance to bind dependencies to. * @return void */ public function bindModelDependencies(ModelInterface $modelInstance): void { $className = $this->resolveProviderClassName($modelInstance->providerMetadata()->getId()); if ($modelInstance instanceof WithHttpTransporterInterface) { $modelInstance->setHttpTransporter($this->getHttpTransporter()); } if ($modelInstance instanceof WithRequestAuthenticationInterface) { $requestAuthentication = $this->getProviderRequestAuthentication($className); if ($requestAuthentication !== null) { $modelInstance->setRequestAuthentication($requestAuthentication); } } } /** * Gets the class name for a registered provider (handles both ID and class name input). * * @param string|class-string<ProviderInterface> $idOrClassName The provider ID or class name. * @return class-string<ProviderInterface> The provider class name. * @throws InvalidArgumentException If provider is not registered. */ private function resolveProviderClassName(string $idOrClassName): string { // If it's already a class name, return it if ($this->isRegisteredClassName($idOrClassName)) { return $idOrClassName; } // If it's a registered ID, return its class name if ($this->isRegisteredId($idOrClassName)) { return $this->registeredIdsToClassNames[$idOrClassName]; } // Not found throw new InvalidArgumentException(sprintf('Provider not registered: %s', $idOrClassName)); } /** * {@inheritDoc} * * @since 0.1.0 */ public function setHttpTransporter(HttpTransporterInterface $httpTransporter): void { $this->setHttpTransporterOriginal($httpTransporter); // Make sure all registered providers have the HTTP transporter hooked up as needed. foreach ($this->registeredIdsToClassNames as $className) { $this->setHttpTransporterForProvider($className, $httpTransporter); } } /** * Sets the request authentication instance for the given provider. * * @since 0.1.0 * * @param string|class-string<ProviderInterface> $idOrClassName The provider ID or class name. * @param RequestAuthenticationInterface $requestAuthentication The request authentication instance. */ public function setProviderRequestAuthentication(string $idOrClassName, RequestAuthenticationInterface $requestAuthentication): void { $className = $this->resolveProviderClassName($idOrClassName); $this->providerAuthenticationInstances[$className] = $requestAuthentication; $this->setRequestAuthenticationForProvider($className, $requestAuthentication); } /** * Gets the request authentication instance for the given provider, if set. * * @since 0.1.0 * * @param string|class-string<ProviderInterface> $idOrClassName The provider ID or class name. * @return ?RequestAuthenticationInterface The request authentication instance, or null if not set. */ public function getProviderRequestAuthentication(string $idOrClassName): ?RequestAuthenticationInterface { $className = $this->resolveProviderClassName($idOrClassName); if (!isset($this->providerAuthenticationInstances[$className])) { return null; } return $this->providerAuthenticationInstances[$className]; } /** * Sets the HTTP transporter for a specific provider, hooking up its class instances. * * @since 0.1.0 * * @param class-string<ProviderInterface> $className The provider class name. * @param HttpTransporterInterface $httpTransporter The HTTP transporter instance. */ private function setHttpTransporterForProvider(string $className, HttpTransporterInterface $httpTransporter): void { $availability = $className::availability(); if ($availability instanceof WithHttpTransporterInterface) { $availability->setHttpTransporter($httpTransporter); } $modelMetadataDirectory = $className::modelMetadataDirectory(); if ($modelMetadataDirectory instanceof WithHttpTransporterInterface) { $modelMetadataDirectory->setHttpTransporter($httpTransporter); } if (is_subclass_of($className, ProviderWithOperationsHandlerInterface::class)) { $operationsHandler = $className::operationsHandler(); if ($operationsHandler instanceof WithHttpTransporterInterface) { $operationsHandler->setHttpTransporter($httpTransporter); } } } /** * Sets the request authentication for a specific provider, hooking up its class instances. * * @since 0.1.0 * * @param class-string<ProviderInterface> $className The provider class name. * @param RequestAuthenticationInterface $requestAuthentication The authentication instance. * * @throws InvalidArgumentException If the authentication instance is not of the expected type. */ private function setRequestAuthenticationForProvider(string $className, RequestAuthenticationInterface $requestAuthentication): void { $authenticationMethod = $className::metadata()->getAuthenticationMethod(); if ($authenticationMethod === null) { throw new InvalidArgumentException(sprintf('Provider %s does not expect any authentication, but got %s.', $className, get_class($requestAuthentication))); } $expectedClass = $authenticationMethod->getImplementationClass(); if (!$requestAuthentication instanceof $expectedClass) { throw new InvalidArgumentException(sprintf('Provider %s expects authentication of type %s, but got %s.', $className, $expectedClass, get_class($requestAuthentication))); } $availability = $className::availability(); if ($availability instanceof WithRequestAuthenticationInterface) { $availability->setRequestAuthentication($requestAuthentication); } $modelMetadataDirectory = $className::modelMetadataDirectory(); if ($modelMetadataDirectory instanceof WithRequestAuthenticationInterface) { $modelMetadataDirectory->setRequestAuthentication($requestAuthentication); } if (is_subclass_of($className, ProviderWithOperationsHandlerInterface::class)) { $operationsHandler = $className::operationsHandler(); if ($operationsHandler instanceof WithRequestAuthenticationInterface) { $operationsHandler->setRequestAuthentication($requestAuthentication); } } } /** * Creates a default request authentication instance for a provider. * * @since 0.1.0 * * @param class-string<ProviderInterface> $className The provider class name. * @return ?RequestAuthenticationInterface The default request authentication instance, or null if not required or * if no credential data can be found. */ private function createDefaultProviderRequestAuthentication(string $className): ?RequestAuthenticationInterface { $providerMetadata = $className::metadata(); $providerId = $providerMetadata->getId(); $authenticationMethod = $providerMetadata->getAuthenticationMethod(); if ($authenticationMethod === null) { return null; } $authenticationClass = $authenticationMethod->getImplementationClass(); if ($authenticationClass === null) { return null; } $authenticationSchema = $authenticationClass::getJsonSchema(); // Iterate over all JSON schema object properties to try to determine the necessary authentication data. $authenticationData = []; if (isset($authenticationSchema['properties']) && is_array($authenticationSchema['properties'])) { /** @var array<string, mixed> $details */ foreach ($authenticationSchema['properties'] as $property => $details) { $envVarName = $this->getEnvVarName($providerId, $property); // Try to get the value from environment variable or constant. $envValue = getenv($envVarName); if ($envValue === \false) { if (!defined($envVarName)) { continue; // Skip if neither environment variable nor constant is defined. } $envValue = constant($envVarName); if (!is_scalar($envValue)) { continue; } } if (isset($details['type'])) { switch ($details['type']) { case 'boolean': $authenticationData[$property] = filter_var($envValue, \FILTER_VALIDATE_BOOLEAN); break; case 'number': $authenticationData[$property] = (int) $envValue; break; case 'string': default: $authenticationData[$property] = (string) $envValue; } } else { // Default to string if no type is specified. $authenticationData[$property] = (string) $envValue; } } // If any required fields are missing, return null to avoid immediate errors. if (isset($authenticationSchema['required']) && is_array($authenticationSchema['required'])) { /** @var list<string> $requiredProperties */ $requiredProperties = $authenticationSchema['required']; if (array_diff_key(array_flip($requiredProperties), $authenticationData)) { return null; } } } /** @var RequestAuthenticationInterface */ /** @var array<string, mixed> $authenticationData */ return $authenticationClass::fromArray($authenticationData); } /** * Checks if the given value is a registered provider class name. * * @since 0.4.0 * * @param string $idOrClassName The value to check. * @return bool True if it's a registered class name. * @phpstan-assert-if-true class-string<ProviderInterface> $idOrClassName */ private function isRegisteredClassName(string $idOrClassName): bool { return isset($this->registeredClassNamesToIds[$idOrClassName]); } /** * Checks if the given value is a registered provider ID. * * @since 0.4.0 * * @param string $idOrClassName The value to check. * @return bool True if it's a registered provider ID. */ private function isRegisteredId(string $idOrClassName): bool { return isset($this->registeredIdsToClassNames[$idOrClassName]); } /** * Converts a provider ID and field name to a constant case environment variable name. * * @since 0.1.0 * * @param string $providerId The provider ID. * @param string $field The field name. * @return string The environment variable name in CONSTANT_CASE. */ private function getEnvVarName(string $providerId, string $field): string { // Convert camelCase or kebab-case or snake_case to CONSTANT_CASE. $constantCaseProviderId = strtoupper((string) preg_replace('/([a-z])([A-Z])/', '$1_$2', str_replace('-', '_', $providerId))); $constantCaseField = strtoupper((string) preg_replace('/([a-z])([A-Z])/', '$1_$2', str_replace('-', '_', $field))); return "{$constantCaseProviderId}_{$constantCaseField}"; } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php�������������������������������0000644�����������������00000002021�15250554011�0022013 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\ApiBasedImplementation\Contracts; use WordPress\AiClient\Providers\Http\DTO\RequestOptions; use WordPress\AiClient\Providers\Models\Contracts\ModelInterface; /** * Interface for API-based AI models that support HTTP transport configuration. * * This interface extends ModelInterface to add request options support * for models that communicate with external APIs via HTTP. * * @since 0.3.0 */ interface ApiBasedModelInterface extends ModelInterface { /** * Sets the request options for HTTP transport. * * @since 0.3.0 * * @param RequestOptions $requestOptions The request options to use. * @return void */ public function setRequestOptions(RequestOptions $requestOptions): void; /** * Gets the request options for HTTP transport. * * @since 0.3.0 * * @return RequestOptions|null The request options, or null if not set. */ public function getRequestOptions(): ?RequestOptions; } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/ApiBasedImplementation/Contracts/error_log������������������������������������������������0000644�����������������00000012006�15250554011�0016751 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������[15-Jul-2026 16:16:49 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Models\Contracts\ModelInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php:16 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php on line 16 [24-Jul-2026 12:15:21 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Models\Contracts\ModelInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php:16 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php on line 16 [25-Jul-2026 05:33:37 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Models\Contracts\ModelInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php:16 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php on line 16 [04-Aug-2026 23:27:26 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Models\Contracts\ModelInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php:16 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php on line 16 [13-Aug-2026 01:33:13 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Models\Contracts\ModelInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php:16 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php on line 16 [13-Aug-2026 12:59:44 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Models\Contracts\ModelInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php:16 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php on line 16 [19-Aug-2026 21:32:06 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Models\Contracts\ModelInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php:16 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php on line 16 [19-Aug-2026 22:06:08 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Models\Contracts\ModelInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php:16 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php on line 16 [19-Aug-2026 23:24:09 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Models\Contracts\ModelInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php:16 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php on line 16 [03-Sep-2026 22:13:45 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Models\Contracts\ModelInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php:16 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php on line 16 [05-Sep-2026 03:48:49 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Models\Contracts\ModelInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php:16 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php on line 16 ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/ApiBasedImplementation/error_log����������������������������������������������������������0000644�����������������00000055520�15250554011�0015021 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������[15-Jul-2026 16:16:47 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModelMetadataDirectory.php on line 21 [15-Jul-2026 16:16:47 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\AbstractProvider" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php:16 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php on line 16 [15-Jul-2026 16:16:48 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModel.php on line 23 [15-Jul-2026 16:16:48 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php on line 18 [15-Jul-2026 16:16:48 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php on line 23 [24-Jul-2026 12:15:20 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModel.php on line 23 [24-Jul-2026 12:15:20 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModelMetadataDirectory.php on line 21 [24-Jul-2026 12:15:21 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\AbstractProvider" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php:16 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php on line 16 [24-Jul-2026 12:15:21 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php on line 23 [24-Jul-2026 12:15:21 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php on line 18 [25-Jul-2026 05:33:35 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\AbstractProvider" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php:16 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php on line 16 [25-Jul-2026 05:33:35 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php on line 23 [25-Jul-2026 05:33:36 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModelMetadataDirectory.php on line 21 [25-Jul-2026 05:33:36 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php on line 18 [25-Jul-2026 05:33:36 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModel.php on line 23 [04-Aug-2026 23:27:24 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModelMetadataDirectory.php on line 21 [04-Aug-2026 23:27:24 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModel.php on line 23 [04-Aug-2026 23:27:24 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\AbstractProvider" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php:16 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php on line 16 [04-Aug-2026 23:27:25 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php on line 23 [04-Aug-2026 23:27:25 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php on line 18 [13-Aug-2026 01:33:10 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModelMetadataDirectory.php on line 21 [13-Aug-2026 01:33:11 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModel.php on line 23 [13-Aug-2026 01:33:11 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php on line 23 [13-Aug-2026 01:33:11 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php on line 18 [13-Aug-2026 01:33:11 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\AbstractProvider" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php:16 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php on line 16 [13-Aug-2026 12:59:43 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModel.php on line 23 [13-Aug-2026 12:59:43 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModelMetadataDirectory.php on line 21 [13-Aug-2026 12:59:43 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\AbstractProvider" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php:16 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php on line 16 [13-Aug-2026 12:59:43 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php on line 23 [13-Aug-2026 12:59:44 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php on line 18 [19-Aug-2026 21:32:06 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php on line 18 [19-Aug-2026 21:32:06 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModelMetadataDirectory.php on line 21 [19-Aug-2026 21:32:06 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php on line 23 [19-Aug-2026 21:32:06 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\AbstractProvider" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php:16 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php on line 16 [19-Aug-2026 21:32:06 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModel.php on line 23 [19-Aug-2026 22:06:06 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php on line 23 [19-Aug-2026 22:06:06 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php on line 18 [19-Aug-2026 22:06:06 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\AbstractProvider" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php:16 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php on line 16 [19-Aug-2026 22:06:07 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModel.php on line 23 [19-Aug-2026 22:06:07 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModelMetadataDirectory.php on line 21 [19-Aug-2026 23:24:06 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModelMetadataDirectory.php on line 21 [19-Aug-2026 23:24:07 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php on line 23 [19-Aug-2026 23:24:07 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php on line 18 [19-Aug-2026 23:24:07 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\AbstractProvider" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php:16 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php on line 16 [19-Aug-2026 23:24:07 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModel.php on line 23 [03-Sep-2026 06:47:11 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModelMetadataDirectory.php on line 21 [03-Sep-2026 06:47:12 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php on line 18 [03-Sep-2026 06:47:13 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php on line 23 [03-Sep-2026 06:48:08 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\AbstractProvider" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php:16 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php on line 16 [03-Sep-2026 06:49:34 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModel.php on line 23 [03-Sep-2026 18:00:55 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModelMetadataDirectory.php on line 21 [03-Sep-2026 18:00:55 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModel.php on line 23 [03-Sep-2026 18:00:57 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php on line 23 [03-Sep-2026 20:36:31 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php on line 18 [03-Sep-2026 20:36:34 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\AbstractProvider" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php:16 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php on line 16 [04-Sep-2026 22:03:08 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php:18 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php on line 18 [04-Sep-2026 22:03:08 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\AbstractProvider" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php:16 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php on line 16 [04-Sep-2026 22:38:20 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModelMetadataDirectory.php on line 21 [04-Sep-2026 22:38:22 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModel.php on line 23 [04-Sep-2026 22:38:24 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php on line 23 ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/ApiBasedImplementation/AbstractApiBasedModel.php������������������������������������������0000644�����������������00000006231�15250554011�0017725 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\ApiBasedImplementation; use WordPress\AiClient\Providers\ApiBasedImplementation\Contracts\ApiBasedModelInterface; use WordPress\AiClient\Providers\DTO\ProviderMetadata; use WordPress\AiClient\Providers\Http\Contracts\WithHttpTransporterInterface; use WordPress\AiClient\Providers\Http\Contracts\WithRequestAuthenticationInterface; use WordPress\AiClient\Providers\Http\DTO\RequestOptions; use WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait; use WordPress\AiClient\Providers\Http\Traits\WithRequestAuthenticationTrait; use WordPress\AiClient\Providers\Models\DTO\ModelConfig; use WordPress\AiClient\Providers\Models\DTO\ModelMetadata; /** * Base class for an API-based model for a provider. * * While this class contains no abstract methods, it is still abstract to ensure that each model class can actually * perform generative AI tasks by implementing the corresponding interfaces. * * @since 0.1.0 */ abstract class AbstractApiBasedModel implements ApiBasedModelInterface, WithHttpTransporterInterface, WithRequestAuthenticationInterface { use WithHttpTransporterTrait; use WithRequestAuthenticationTrait; /** * @var ModelMetadata The metadata for the model. */ private ModelMetadata $metadata; /** * @var ProviderMetadata The metadata for the model's provider. */ private ProviderMetadata $providerMetadata; /** * @var ModelConfig The configuration for the model. */ private ModelConfig $config; /** * @var RequestOptions|null The request options for HTTP transport. */ private ?RequestOptions $requestOptions = null; /** * Constructor. * * @since 0.1.0 * * @param ModelMetadata $metadata The metadata for the model. * @param ProviderMetadata $providerMetadata The metadata for the model's provider. */ public function __construct(ModelMetadata $metadata, ProviderMetadata $providerMetadata) { $this->metadata = $metadata; $this->providerMetadata = $providerMetadata; $this->config = ModelConfig::fromArray([]); } /** * {@inheritDoc} * * @since 0.1.0 */ final public function metadata(): ModelMetadata { return $this->metadata; } /** * {@inheritDoc} * * @since 0.1.0 */ final public function providerMetadata(): ProviderMetadata { return $this->providerMetadata; } /** * {@inheritDoc} * * @since 0.1.0 */ final public function setConfig(ModelConfig $config): void { $this->config = $config; } /** * {@inheritDoc} * * @since 0.1.0 */ final public function getConfig(): ModelConfig { return $this->config; } /** * {@inheritDoc} * * @since 0.3.0 */ final public function setRequestOptions(RequestOptions $requestOptions): void { $this->requestOptions = $requestOptions; } /** * {@inheritDoc} * * @since 0.3.0 */ final public function getRequestOptions(): ?RequestOptions { return $this->requestOptions; } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/ApiBasedImplementation/AbstractApiBasedModelMetadataDirectory.php�������������������������0000644�����������������00000006365�15250554011�0023263 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\ApiBasedImplementation; use WordPress\AiClient\AiClient; use WordPress\AiClient\Common\Contracts\CachesDataInterface; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Common\Traits\WithDataCachingTrait; use WordPress\AiClient\Providers\Contracts\ModelMetadataDirectoryInterface; use WordPress\AiClient\Providers\Http\Contracts\WithHttpTransporterInterface; use WordPress\AiClient\Providers\Http\Contracts\WithRequestAuthenticationInterface; use WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait; use WordPress\AiClient\Providers\Http\Traits\WithRequestAuthenticationTrait; use WordPress\AiClient\Providers\Models\DTO\ModelMetadata; /** * Base class for an API-based model metadata directory for a provider. * * @since 0.1.0 */ abstract class AbstractApiBasedModelMetadataDirectory implements ModelMetadataDirectoryInterface, WithHttpTransporterInterface, WithRequestAuthenticationInterface, CachesDataInterface { use WithHttpTransporterTrait; use WithRequestAuthenticationTrait; use WithDataCachingTrait; /** * The cache key suffix for the models list. * * @since 0.4.0 * * @var string */ private const MODELS_CACHE_KEY = 'models'; /** * {@inheritDoc} * * @since 0.1.0 */ final public function listModelMetadata(): array { $modelsMetadata = $this->getModelMetadataMap(); return array_values($modelsMetadata); } /** * {@inheritDoc} * * @since 0.1.0 */ final public function hasModelMetadata(string $modelId): bool { $modelsMetadata = $this->getModelMetadataMap(); return isset($modelsMetadata[$modelId]); } /** * {@inheritDoc} * * @since 0.1.0 */ final public function getModelMetadata(string $modelId): ModelMetadata { $modelsMetadata = $this->getModelMetadataMap(); if (!isset($modelsMetadata[$modelId])) { throw new InvalidArgumentException(sprintf('No model with ID %s was found in the provider', $modelId)); } return $modelsMetadata[$modelId]; } /** * Returns the map of model ID to model metadata for all models from the provider. * * @since 0.1.0 * * @return array<string, ModelMetadata> Map of model ID to model metadata. */ private function getModelMetadataMap(): array { /** @var array<string, ModelMetadata> */ return $this->cached(self::MODELS_CACHE_KEY, fn() => $this->sendListModelsRequest(), 86400); } /** * {@inheritDoc} * * @since 0.4.0 */ protected function getCachedKeys(): array { return [self::MODELS_CACHE_KEY]; } /** * {@inheritDoc} * * @since 0.4.0 */ protected function getBaseCacheKey(): string { return 'ai_client_' . AiClient::VERSION . '_' . md5(static::class); } /** * Sends the API request to list models from the provider and returns the map of model ID to model metadata. * * @since 0.1.0 * * @return array<string, ModelMetadata> Map of model ID to model metadata. */ abstract protected function sendListModelsRequest(): array; } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php�������������������������0000644�����������������00000003406�15250554011�0023327 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\ApiBasedImplementation; use Exception; use WordPress\AiClient\Providers\Contracts\ModelMetadataDirectoryInterface; use WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface; /** * Class to check availability for an API-based provider via a test request to the endpoint to list models. * * This class should be used for cloud-based providers that offer a model listing endpoint which requires * authentication. A request to this endpoint is used to determine if the provider is properly configured * with valid credentials. * * @since 0.1.0 */ class ListModelsApiBasedProviderAvailability implements ProviderAvailabilityInterface { /** * @var ModelMetadataDirectoryInterface The model metadata directory to use for checking availability. */ private ModelMetadataDirectoryInterface $modelMetadataDirectory; /** * Constructor. * * @since 0.1.0 * * @param ModelMetadataDirectoryInterface $modelMetadataDirectory The model metadata directory to use for checking * availability. */ public function __construct(ModelMetadataDirectoryInterface $modelMetadataDirectory) { $this->modelMetadataDirectory = $modelMetadataDirectory; } /** * {@inheritDoc} * * @since 0.1.0 */ public function isConfigured(): bool { try { // Attempt to list models to check if the provider is available. $this->modelMetadataDirectory->listModelMetadata(); return \true; } catch (Exception $e) { // If an exception occurs, the provider is not available. return \false; } } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php�����������������������0000644�����������������00000004500�15250554011�0023643 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\ApiBasedImplementation; use Exception; use WordPress\AiClient\Messages\DTO\Message; use WordPress\AiClient\Messages\DTO\MessagePart; use WordPress\AiClient\Messages\Enums\MessageRoleEnum; use WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface; use WordPress\AiClient\Providers\Models\Contracts\ModelInterface; use WordPress\AiClient\Providers\Models\DTO\ModelConfig; use WordPress\AiClient\Providers\Models\TextGeneration\Contracts\TextGenerationModelInterface; /** * Class to check availability for an API-based provider via a test request to the endpoint to generate text. * * This class should be used for cloud-based providers that do not offer a model listing endpoint, but do offer a * text generation endpoint which requires authentication. A minimal request to this endpoint is used to determine * if the provider is properly configured with valid credentials. * * @since 0.1.0 */ class GenerateTextApiBasedProviderAvailability implements ProviderAvailabilityInterface { /** * @var ModelInterface&TextGenerationModelInterface The model to use for checking availability. */ private ModelInterface $model; /** * Constructor. * * @since 0.1.0 * * @param ModelInterface $model The model to use for checking availability. */ public function __construct(ModelInterface $model) { if (!$model instanceof TextGenerationModelInterface) { throw new Exception('The model class to check provider availability must implement TextGenerationModelInterface.'); } $this->model = $model; } /** * {@inheritDoc} * * @since 0.1.0 */ public function isConfigured(): bool { // Set config to use as few resources as possible for the test. $modelConfig = ModelConfig::fromArray([ModelConfig::KEY_MAX_TOKENS => 1]); $this->model->setConfig($modelConfig); try { // Attempt to generate text to check if the provider is available. $this->model->generateTextResult([new Message(MessageRoleEnum::user(), [new MessagePart('a')])]); return \true; } catch (Exception $e) { // If an exception occurs, the provider is not available. return \false; } } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Providers/ApiBasedImplementation/AbstractApiProvider.php��������������������������������������������0000644�����������������00000003001�15250554011�0017510 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Providers\ApiBasedImplementation; use WordPress\AiClient\Providers\AbstractProvider; /** * Base class for API-based providers. * * This abstract class provides URL construction utilities for providers that * communicate with REST APIs. It standardizes the pattern of combining a base * URL with endpoint paths. * * @since 0.2.0 */ abstract class AbstractApiProvider extends AbstractProvider { /** * Gets the base URL for the provider's API. * * The base URL should include the protocol and domain, and may include * the API version path (e.g., "https://api.example.com/v1"). * * @since 0.2.0 * * @return string The base URL for the provider's API. */ abstract protected static function baseUrl(): string; /** * Constructs a full URL by combining the base URL with an optional path. * * This method ensures proper URL construction by: * - Using the provider's base URL * - Trimming leading slashes from the path to prevent double-slashes * - Joining the base URL and path with a single forward slash * * @since 0.2.0 * * @param string $path Optional path to append to the base URL. Default empty string. * @return string The complete URL. */ public static function url(string $path = ''): string { if ($path === '') { return static::baseUrl(); } return static::baseUrl() . '/' . ltrim($path, '/'); } } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Tools/DTO/FunctionResponse.php����������������������������������������������������������������������0000644�����������������00000007313�15250554011�0012311 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Tools\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; use WordPress\AiClient\Common\Exception\InvalidArgumentException; /** * Represents a response to a function call. * * This DTO encapsulates the result of executing a function that was * requested by the AI model through a FunctionCall. * * @since 0.1.0 * * @phpstan-type FunctionResponseArrayShape array{id?: string, name?: string, response: mixed} * * @extends AbstractDataTransferObject<FunctionResponseArrayShape> */ class FunctionResponse extends AbstractDataTransferObject { public const KEY_ID = 'id'; public const KEY_NAME = 'name'; public const KEY_RESPONSE = 'response'; /** * @var string|null The ID of the function call this is responding to. */ private ?string $id; /** * @var string|null The name of the function that was called. */ private ?string $name; /** * @var mixed The response data from the function. */ private $response; /** * Constructor. * * @since 0.1.0 * * @param string|null $id The ID of the function call this is responding to. * @param string|null $name The name of the function that was called. * @param mixed $response The response data from the function. * @throws InvalidArgumentException If neither id nor name is provided. */ public function __construct(?string $id, ?string $name, $response) { if ($id === null && $name === null) { throw new InvalidArgumentException('At least one of id or name must be provided.'); } $this->id = $id; $this->name = $name; $this->response = $response; } /** * Gets the function call ID. * * @since 0.1.0 * * @return string|null The function call ID. */ public function getId(): ?string { return $this->id; } /** * Gets the function name. * * @since 0.1.0 * * @return string|null The function name. */ public function getName(): ?string { return $this->name; } /** * Gets the function response. * * @since 0.1.0 * * @return mixed The response data. */ public function getResponse() { return $this->response; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_ID => ['type' => 'string', 'description' => 'The ID of the function call this is responding to.'], self::KEY_NAME => ['type' => 'string', 'description' => 'The name of the function that was called.'], self::KEY_RESPONSE => ['type' => ['string', 'number', 'boolean', 'object', 'array', 'null'], 'description' => 'The response data from the function.']], 'anyOf' => [['required' => [self::KEY_RESPONSE, self::KEY_ID]], ['required' => [self::KEY_RESPONSE, self::KEY_NAME]]]]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return FunctionResponseArrayShape */ public function toArray(): array { $data = []; if ($this->id !== null) { $data[self::KEY_ID] = $this->id; } if ($this->name !== null) { $data[self::KEY_NAME] = $this->name; } $data[self::KEY_RESPONSE] = $this->response; return $data; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function fromArray(array $array): self { static::validateFromArrayData($array, [self::KEY_RESPONSE]); return new self($array[self::KEY_ID] ?? null, $array[self::KEY_NAME] ?? null, $array[self::KEY_RESPONSE]); } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Tools/DTO/error_log���������������������������������������������������������������������������������0000644�����������������00000045620�15250554011�0010214 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������[15-Jul-2026 16:16:38 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php on line 20 [15-Jul-2026 16:16:38 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php on line 19 [15-Jul-2026 16:16:38 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php on line 23 [15-Jul-2026 16:16:38 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php on line 20 [24-Jul-2026 12:15:18 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php on line 20 [24-Jul-2026 12:15:19 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php on line 19 [24-Jul-2026 12:15:19 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php on line 20 [24-Jul-2026 12:15:19 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php on line 23 [25-Jul-2026 05:33:42 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php on line 23 [25-Jul-2026 05:33:42 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php on line 19 [25-Jul-2026 05:33:43 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php on line 20 [25-Jul-2026 05:33:43 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php on line 20 [04-Aug-2026 23:27:57 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php on line 23 [04-Aug-2026 23:27:57 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php on line 19 [04-Aug-2026 23:27:58 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php on line 20 [04-Aug-2026 23:27:58 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php on line 20 [13-Aug-2026 01:33:07 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php on line 19 [13-Aug-2026 01:33:08 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php on line 23 [13-Aug-2026 01:33:08 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php on line 20 [13-Aug-2026 01:33:08 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php on line 20 [13-Aug-2026 13:00:16 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php on line 20 [13-Aug-2026 13:00:16 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php on line 19 [13-Aug-2026 13:00:17 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php on line 23 [13-Aug-2026 13:00:17 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php on line 20 [19-Aug-2026 21:32:07 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php on line 23 [19-Aug-2026 21:32:07 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php on line 20 [19-Aug-2026 21:32:07 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php on line 19 [19-Aug-2026 21:32:07 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php on line 20 [19-Aug-2026 22:05:36 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php on line 20 [19-Aug-2026 22:05:37 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php on line 20 [19-Aug-2026 22:05:37 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php on line 23 [19-Aug-2026 22:05:37 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php on line 19 [19-Aug-2026 23:24:46 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php on line 20 [19-Aug-2026 23:24:46 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php on line 19 [19-Aug-2026 23:24:46 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php on line 23 [19-Aug-2026 23:24:46 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php on line 20 [03-Sep-2026 06:47:01 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php on line 19 [03-Sep-2026 06:47:46 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php on line 20 [03-Sep-2026 06:47:59 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php on line 23 [03-Sep-2026 06:49:03 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php on line 20 [03-Sep-2026 23:30:28 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php on line 23 [03-Sep-2026 23:30:28 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php on line 20 [03-Sep-2026 23:30:30 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php on line 20 [03-Sep-2026 23:30:43 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php on line 19 [05-Sep-2026 07:01:31 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php on line 23 [05-Sep-2026 07:01:32 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php on line 20 [05-Sep-2026 07:01:33 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php on line 20 [05-Sep-2026 07:02:01 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php on line 19 [08-Sep-2026 18:12:49 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php on line 23 [08-Sep-2026 18:12:50 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php on line 20 [08-Sep-2026 18:12:50 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php:20 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php on line 20 [08-Sep-2026 18:56:20 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php:19 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php on line 19 ����������������������������������������������������������������������������������������������������������������Tools/DTO/FunctionCall.php��������������������������������������������������������������������������0000644�����������������00000007133�15250554011�0011366 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Tools\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; use WordPress\AiClient\Common\Exception\InvalidArgumentException; /** * Represents a function call request from an AI model. * * This DTO encapsulates information about a function that the AI model * wants to invoke, including the function name and its arguments. * * @since 0.1.0 * * @phpstan-type FunctionCallArrayShape array{id?: string, name?: string, args?: mixed} * * @extends AbstractDataTransferObject<FunctionCallArrayShape> */ class FunctionCall extends AbstractDataTransferObject { public const KEY_ID = 'id'; public const KEY_NAME = 'name'; public const KEY_ARGS = 'args'; /** * @var string|null Unique identifier for this function call. */ private ?string $id; /** * @var string|null The name of the function to call. */ private ?string $name; /** * @var mixed The arguments to pass to the function. */ private $args; /** * Constructor. * * @since 0.1.0 * * @param string|null $id Unique identifier for this function call. * @param string|null $name The name of the function to call. * @param mixed $args The arguments to pass to the function. * @throws InvalidArgumentException If neither id nor name is provided. */ public function __construct(?string $id = null, ?string $name = null, $args = null) { if ($id === null && $name === null) { throw new InvalidArgumentException('At least one of id or name must be provided.'); } $this->id = $id; $this->name = $name; $this->args = $args; } /** * Gets the function call ID. * * @since 0.1.0 * * @return string|null The function call ID. */ public function getId(): ?string { return $this->id; } /** * Gets the function name. * * @since 0.1.0 * * @return string|null The function name. */ public function getName(): ?string { return $this->name; } /** * Gets the function arguments. * * @since 0.1.0 * * @return mixed The function arguments. */ public function getArgs() { return $this->args; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_ID => ['type' => 'string', 'description' => 'Unique identifier for this function call.'], self::KEY_NAME => ['type' => 'string', 'description' => 'The name of the function to call.'], self::KEY_ARGS => ['type' => ['string', 'number', 'boolean', 'object', 'array', 'null'], 'description' => 'The arguments to pass to the function.']], 'anyOf' => [['required' => [self::KEY_ID]], ['required' => [self::KEY_NAME]]]]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return FunctionCallArrayShape */ public function toArray(): array { $data = []; if ($this->id !== null) { $data[self::KEY_ID] = $this->id; } if ($this->name !== null) { $data[self::KEY_NAME] = $this->name; } if ($this->args !== null) { $data[self::KEY_ARGS] = $this->args; } return $data; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function fromArray(array $array): self { return new self($array[self::KEY_ID] ?? null, $array[self::KEY_NAME] ?? null, $array[self::KEY_ARGS] ?? null); } } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Tools/DTO/WebSearch.php�����������������������������������������������������������������������������0000644�����������������00000005502�15250554011�0010646 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Tools\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; /** * Represents web search configuration for AI models. * * This DTO defines constraints for web searches that AI models can perform, * including allowed and disallowed domains. * * @since 0.1.0 * * @phpstan-type WebSearchArrayShape array{allowedDomains?: string[], disallowedDomains?: string[]} * * @extends AbstractDataTransferObject<WebSearchArrayShape> */ class WebSearch extends AbstractDataTransferObject { public const KEY_ALLOWED_DOMAINS = 'allowedDomains'; public const KEY_DISALLOWED_DOMAINS = 'disallowedDomains'; /** * @var string[] List of domains that are allowed for web search. */ private array $allowedDomains; /** * @var string[] List of domains that are disallowed for web search. */ private array $disallowedDomains; /** * Constructor. * * @since 0.1.0 * * @param string[] $allowedDomains List of domains that are allowed for web search. * @param string[] $disallowedDomains List of domains that are disallowed for web search. */ public function __construct(array $allowedDomains = [], array $disallowedDomains = []) { $this->allowedDomains = $allowedDomains; $this->disallowedDomains = $disallowedDomains; } /** * Gets the allowed domains. * * @since 0.1.0 * * @return string[] The allowed domains. */ public function getAllowedDomains(): array { return $this->allowedDomains; } /** * Gets the disallowed domains. * * @since 0.1.0 * * @return string[] The disallowed domains. */ public function getDisallowedDomains(): array { return $this->disallowedDomains; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_ALLOWED_DOMAINS => ['type' => 'array', 'items' => ['type' => 'string'], 'description' => 'List of domains that are allowed for web search.'], self::KEY_DISALLOWED_DOMAINS => ['type' => 'array', 'items' => ['type' => 'string'], 'description' => 'List of domains that are disallowed for web search.']], 'required' => []]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return WebSearchArrayShape */ public function toArray(): array { return [self::KEY_ALLOWED_DOMAINS => $this->allowedDomains, self::KEY_DISALLOWED_DOMAINS => $this->disallowedDomains]; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function fromArray(array $array): self { return new self($array[self::KEY_ALLOWED_DOMAINS] ?? [], $array[self::KEY_DISALLOWED_DOMAINS] ?? []); } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Tools/DTO/FunctionDeclaration.php�������������������������������������������������������������������0000644�����������������00000007005�15250554011�0012736 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Tools\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; /** * Represents a function declaration for AI models. * * This DTO describes a function that can be called by the AI model, * including its name, description, and parameter schema. * * @since 0.1.0 * * @phpstan-type FunctionDeclarationArrayShape array{ * name: string, * description: string, * parameters?: array<string, mixed> * } * * @extends AbstractDataTransferObject<FunctionDeclarationArrayShape> */ class FunctionDeclaration extends AbstractDataTransferObject { public const KEY_NAME = 'name'; public const KEY_DESCRIPTION = 'description'; public const KEY_PARAMETERS = 'parameters'; /** * @var string The name of the function. */ private string $name; /** * @var string A description of what the function does. */ private string $description; /** * @var array<string, mixed>|null The JSON schema for the function parameters. */ private ?array $parameters; /** * Constructor. * * @since 0.1.0 * * @param string $name The name of the function. * @param string $description A description of what the function does. * @param array<string, mixed>|null $parameters The JSON schema for the function parameters. */ public function __construct(string $name, string $description, ?array $parameters = null) { $this->name = $name; $this->description = $description; $this->parameters = $parameters; } /** * Gets the function name. * * @since 0.1.0 * * @return string The function name. */ public function getName(): string { return $this->name; } /** * Gets the function description. * * @since 0.1.0 * * @return string The function description. */ public function getDescription(): string { return $this->description; } /** * Gets the function parameters schema. * * @since 0.1.0 * * @return array<string, mixed>|null The parameters schema. */ public function getParameters(): ?array { return $this->parameters; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_NAME => ['type' => 'string', 'description' => 'The name of the function.'], self::KEY_DESCRIPTION => ['type' => 'string', 'description' => 'A description of what the function does.'], self::KEY_PARAMETERS => ['type' => 'object', 'description' => 'The JSON schema for the function parameters.', 'additionalProperties' => \true]], 'required' => [self::KEY_NAME, self::KEY_DESCRIPTION]]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return FunctionDeclarationArrayShape */ public function toArray(): array { $data = [self::KEY_NAME => $this->name, self::KEY_DESCRIPTION => $this->description]; if ($this->parameters !== null) { $data[self::KEY_PARAMETERS] = $this->parameters; } return $data; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function fromArray(array $array): self { static::validateFromArrayData($array, [self::KEY_NAME, self::KEY_DESCRIPTION]); return new self($array[self::KEY_NAME], $array[self::KEY_DESCRIPTION], $array[self::KEY_PARAMETERS] ?? null); } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Results/DTO/error_log�������������������������������������������������������������������������������0000644�����������������00000034306�15250554011�0010554 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������[15-Jul-2026 16:16:40 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/Candidate.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/Candidate.php on line 24 [15-Jul-2026 16:16:40 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php:38 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php on line 38 [15-Jul-2026 16:16:40 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php:27 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php on line 27 [24-Jul-2026 12:15:16 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php:27 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php on line 27 [24-Jul-2026 12:15:16 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/Candidate.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/Candidate.php on line 24 [24-Jul-2026 12:15:16 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php:38 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php on line 38 [25-Jul-2026 05:33:07 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/Candidate.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/Candidate.php on line 24 [25-Jul-2026 05:33:07 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php:27 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php on line 27 [25-Jul-2026 05:33:07 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php:38 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php on line 38 [04-Aug-2026 23:27:52 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php:27 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php on line 27 [04-Aug-2026 23:27:53 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php:38 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php on line 38 [04-Aug-2026 23:27:53 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/Candidate.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/Candidate.php on line 24 [13-Aug-2026 01:33:03 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php:38 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php on line 38 [13-Aug-2026 01:33:03 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php:27 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php on line 27 [13-Aug-2026 01:33:04 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/Candidate.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/Candidate.php on line 24 [13-Aug-2026 13:00:08 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php:27 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php on line 27 [13-Aug-2026 13:00:08 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/Candidate.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/Candidate.php on line 24 [13-Aug-2026 13:00:09 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php:38 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php on line 38 [19-Aug-2026 21:32:05 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/Candidate.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/Candidate.php on line 24 [19-Aug-2026 21:32:05 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php:27 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php on line 27 [19-Aug-2026 21:32:05 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php:38 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php on line 38 [19-Aug-2026 22:05:24 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php:38 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php on line 38 [19-Aug-2026 22:05:24 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php:27 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php on line 27 [19-Aug-2026 22:05:24 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/Candidate.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/Candidate.php on line 24 [19-Aug-2026 23:24:39 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php:27 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php on line 27 [19-Aug-2026 23:24:40 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php:38 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php on line 38 [19-Aug-2026 23:24:40 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/Candidate.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/Candidate.php on line 24 [03-Sep-2026 06:48:42 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/Candidate.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/Candidate.php on line 24 [03-Sep-2026 06:49:15 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php:27 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php on line 27 [03-Sep-2026 06:53:06 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php:38 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php on line 38 [04-Sep-2026 17:00:04 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php:38 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php on line 38 [04-Sep-2026 19:25:33 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php:27 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php on line 27 [05-Sep-2026 12:25:21 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/Candidate.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/Candidate.php on line 24 [05-Sep-2026 19:44:30 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php:38 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php on line 38 [06-Sep-2026 02:19:17 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php:27 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php on line 27 [06-Sep-2026 14:46:46 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/Candidate.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/Candidate.php on line 24 [10-Sep-2026 09:47:37 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php:27 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php on line 27 [10-Sep-2026 09:47:38 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php:38 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php on line 38 [10-Sep-2026 09:47:39 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/Candidate.php:24 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/DTO/Candidate.php on line 24 ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Results/DTO/Candidate.php���������������������������������������������������������������������������0000644�����������������00000006647�15250554011�0011233 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Results\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Messages\DTO\Message; use WordPress\AiClient\Results\Enums\FinishReasonEnum; /** * Represents a candidate response from an AI model. * * When generating content, AI models can produce multiple candidates. * Each candidate contains a message and metadata about why generation stopped. * * @since 0.1.0 * * @phpstan-import-type MessageArrayShape from Message * * @phpstan-type CandidateArrayShape array{message: MessageArrayShape, finishReason: string} * * @extends AbstractDataTransferObject<CandidateArrayShape> */ class Candidate extends AbstractDataTransferObject { public const KEY_MESSAGE = 'message'; public const KEY_FINISH_REASON = 'finishReason'; /** * @var Message The generated message. */ private Message $message; /** * @var FinishReasonEnum The reason generation stopped. */ private FinishReasonEnum $finishReason; /** * Constructor. * * @since 0.1.0 * * @param Message $message The generated message. * @param FinishReasonEnum $finishReason The reason generation stopped. */ public function __construct(Message $message, FinishReasonEnum $finishReason) { if (!$message->getRole()->isModel()) { throw new InvalidArgumentException('Message must be a model message.'); } $this->message = $message; $this->finishReason = $finishReason; } /** * Gets the generated message. * * @since 0.1.0 * * @return Message The message. */ public function getMessage(): Message { return $this->message; } /** * Gets the finish reason. * * @since 0.1.0 * * @return FinishReasonEnum The finish reason. */ public function getFinishReason(): FinishReasonEnum { return $this->finishReason; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_MESSAGE => Message::getJsonSchema(), self::KEY_FINISH_REASON => ['type' => 'string', 'enum' => FinishReasonEnum::getValues(), 'description' => 'The reason generation stopped.']], 'required' => [self::KEY_MESSAGE, self::KEY_FINISH_REASON]]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return CandidateArrayShape */ public function toArray(): array { return [self::KEY_MESSAGE => $this->message->toArray(), self::KEY_FINISH_REASON => $this->finishReason->value]; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function fromArray(array $array): self { static::validateFromArrayData($array, [self::KEY_MESSAGE, self::KEY_FINISH_REASON]); $messageData = $array[self::KEY_MESSAGE]; return new self(Message::fromArray($messageData), FinishReasonEnum::from($array[self::KEY_FINISH_REASON])); } /** * Performs a deep clone of the candidate. * * This method ensures that the message object is cloned to prevent * modifications to the cloned candidate from affecting the original. * * @since 0.4.2 */ public function __clone() { $this->message = clone $this->message; } } �����������������������������������������������������������������������������������������Results/DTO/GenerativeAiResult.php������������������������������������������������������������������0000644�����������������00000032757�15250554011�0013122 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Results\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Common\Exception\RuntimeException; use WordPress\AiClient\Files\DTO\File; use WordPress\AiClient\Messages\DTO\Message; use WordPress\AiClient\Providers\DTO\ProviderMetadata; use WordPress\AiClient\Providers\Models\DTO\ModelMetadata; use WordPress\AiClient\Results\Contracts\ResultInterface; /** * Represents the result of a generative AI operation. * * This DTO contains the generated candidates along with usage statistics * and metadata from the AI provider. * * @since 0.1.0 * * @phpstan-import-type CandidateArrayShape from Candidate * @phpstan-import-type TokenUsageArrayShape from TokenUsage * @phpstan-import-type ProviderMetadataArrayShape from ProviderMetadata * @phpstan-import-type ModelMetadataArrayShape from ModelMetadata * * @phpstan-type GenerativeAiResultArrayShape array{ * id: string, * candidates: array<CandidateArrayShape>, * tokenUsage: TokenUsageArrayShape, * providerMetadata: ProviderMetadataArrayShape, * modelMetadata: ModelMetadataArrayShape, * additionalData?: array<string, mixed> * } * * @extends AbstractDataTransferObject<GenerativeAiResultArrayShape> */ class GenerativeAiResult extends AbstractDataTransferObject implements ResultInterface { public const KEY_ID = 'id'; public const KEY_CANDIDATES = 'candidates'; public const KEY_TOKEN_USAGE = 'tokenUsage'; public const KEY_PROVIDER_METADATA = 'providerMetadata'; public const KEY_MODEL_METADATA = 'modelMetadata'; public const KEY_ADDITIONAL_DATA = 'additionalData'; /** * @var string Unique identifier for this result. */ private string $id; /** * @var Candidate[] The generated candidates. */ private array $candidates; /** * @var TokenUsage Token usage statistics. */ private \WordPress\AiClient\Results\DTO\TokenUsage $tokenUsage; /** * @var ProviderMetadata Provider metadata. */ private ProviderMetadata $providerMetadata; /** * @var ModelMetadata Model metadata. */ private ModelMetadata $modelMetadata; /** * @var array<string, mixed> Additional data. */ private array $additionalData; /** * Constructor. * * @since 0.1.0 * * @param string $id Unique identifier for this result. * @param Candidate[] $candidates The generated candidates. * @param TokenUsage $tokenUsage Token usage statistics. * @param ProviderMetadata $providerMetadata Provider metadata. * @param ModelMetadata $modelMetadata Model metadata. * @param array<string, mixed> $additionalData Additional data. * @throws InvalidArgumentException If no candidates provided. */ public function __construct(string $id, array $candidates, \WordPress\AiClient\Results\DTO\TokenUsage $tokenUsage, ProviderMetadata $providerMetadata, ModelMetadata $modelMetadata, array $additionalData = []) { if (empty($candidates)) { throw new InvalidArgumentException('At least one candidate must be provided'); } $this->id = $id; $this->candidates = $candidates; $this->tokenUsage = $tokenUsage; $this->providerMetadata = $providerMetadata; $this->modelMetadata = $modelMetadata; $this->additionalData = $additionalData; } /** * {@inheritDoc} * * @since 0.1.0 */ public function getId(): string { return $this->id; } /** * Gets the generated candidates. * * @since 0.1.0 * * @return Candidate[] The candidates. */ public function getCandidates(): array { return $this->candidates; } /** * {@inheritDoc} * * @since 0.1.0 */ public function getTokenUsage(): \WordPress\AiClient\Results\DTO\TokenUsage { return $this->tokenUsage; } /** * Gets the provider metadata. * * @since 0.1.0 * * @return ProviderMetadata The provider metadata. */ public function getProviderMetadata(): ProviderMetadata { return $this->providerMetadata; } /** * Gets the model metadata. * * @since 0.1.0 * * @return ModelMetadata The model metadata. */ public function getModelMetadata(): ModelMetadata { return $this->modelMetadata; } /** * {@inheritDoc} * * @since 0.1.0 */ public function getAdditionalData(): array { return $this->additionalData; } /** * Gets the total number of candidates. * * @since 0.1.0 * * @return int The total number of candidates. */ public function getCandidateCount(): int { return count($this->candidates); } /** * Checks if the result has multiple candidates. * * @since 0.1.0 * * @return bool True if there are multiple candidates, false otherwise. */ public function hasMultipleCandidates(): bool { return $this->getCandidateCount() > 1; } /** * Converts the first candidate to text. * * Only text from the content channel is considered. Text within model thought or reasoning is ignored. * * @since 0.1.0 * * @return string The text content. * @throws RuntimeException If no text content. */ public function toText(): string { $message = $this->candidates[0]->getMessage(); foreach ($message->getParts() as $part) { $channel = $part->getChannel(); $text = $part->getText(); if ($channel->isContent() && $text !== null) { return $text; } } throw new RuntimeException('No text content found in first candidate'); } /** * Converts the first candidate to a file. * * Only files from the content channel are considered. Files within model thought or reasoning are ignored. * * @since 0.1.0 * * @return File The file. * @throws RuntimeException If no file content. */ public function toFile(): File { $message = $this->candidates[0]->getMessage(); foreach ($message->getParts() as $part) { $channel = $part->getChannel(); $file = $part->getFile(); if ($channel->isContent() && $file !== null) { return $file; } } throw new RuntimeException('No file content found in first candidate'); } /** * Converts the first candidate to an image file. * * @since 0.1.0 * * @return File The image file. * @throws RuntimeException If no image content. */ public function toImageFile(): File { $file = $this->toFile(); if (!$file->isImage()) { throw new RuntimeException(sprintf('File is not an image. MIME type: %s', $file->getMimeType())); } return $file; } /** * Converts the first candidate to an audio file. * * @since 0.1.0 * * @return File The audio file. * @throws RuntimeException If no audio content. */ public function toAudioFile(): File { $file = $this->toFile(); if (!$file->isAudio()) { throw new RuntimeException(sprintf('File is not an audio file. MIME type: %s', $file->getMimeType())); } return $file; } /** * Converts the first candidate to a video file. * * @since 0.1.0 * * @return File The video file. * @throws RuntimeException If no video content. */ public function toVideoFile(): File { $file = $this->toFile(); if (!$file->isVideo()) { throw new RuntimeException(sprintf('File is not a video file. MIME type: %s', $file->getMimeType())); } return $file; } /** * Converts the first candidate to a message. * * @since 0.1.0 * * @return Message The message. */ public function toMessage(): Message { return $this->candidates[0]->getMessage(); } /** * Converts all candidates to text. * * @since 0.1.0 * * @return list<string> Array of text content. */ public function toTexts(): array { $texts = []; foreach ($this->candidates as $candidate) { $message = $candidate->getMessage(); foreach ($message->getParts() as $part) { $channel = $part->getChannel(); $text = $part->getText(); if ($channel->isContent() && $text !== null) { $texts[] = $text; break; } } } return $texts; } /** * Converts all candidates to files. * * @since 0.1.0 * * @return list<File> Array of files. */ public function toFiles(): array { $files = []; foreach ($this->candidates as $candidate) { $message = $candidate->getMessage(); foreach ($message->getParts() as $part) { $channel = $part->getChannel(); $file = $part->getFile(); if ($channel->isContent() && $file !== null) { $files[] = $file; break; } } } return $files; } /** * Converts all candidates to image files. * * @since 0.1.0 * * @return list<File> Array of image files. */ public function toImageFiles(): array { return array_values(array_filter($this->toFiles(), fn(File $file) => $file->isImage())); } /** * Converts all candidates to audio files. * * @since 0.1.0 * * @return list<File> Array of audio files. */ public function toAudioFiles(): array { return array_values(array_filter($this->toFiles(), fn(File $file) => $file->isAudio())); } /** * Converts all candidates to video files. * * @since 0.1.0 * * @return list<File> Array of video files. */ public function toVideoFiles(): array { return array_values(array_filter($this->toFiles(), fn(File $file) => $file->isVideo())); } /** * Converts all candidates to messages. * * @since 0.1.0 * * @return list<Message> Array of messages. */ public function toMessages(): array { return array_values(array_map(fn(\WordPress\AiClient\Results\DTO\Candidate $candidate) => $candidate->getMessage(), $this->candidates)); } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_ID => ['type' => 'string', 'description' => 'Unique identifier for this result.'], self::KEY_CANDIDATES => ['type' => 'array', 'items' => \WordPress\AiClient\Results\DTO\Candidate::getJsonSchema(), 'minItems' => 1, 'description' => 'The generated candidates.'], self::KEY_TOKEN_USAGE => \WordPress\AiClient\Results\DTO\TokenUsage::getJsonSchema(), self::KEY_PROVIDER_METADATA => ProviderMetadata::getJsonSchema(), self::KEY_MODEL_METADATA => ModelMetadata::getJsonSchema(), self::KEY_ADDITIONAL_DATA => ['type' => 'object', 'additionalProperties' => \true, 'description' => 'Additional data included in the API response.']], 'required' => [self::KEY_ID, self::KEY_CANDIDATES, self::KEY_TOKEN_USAGE, self::KEY_PROVIDER_METADATA, self::KEY_MODEL_METADATA]]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return GenerativeAiResultArrayShape */ public function toArray(): array { return [self::KEY_ID => $this->id, self::KEY_CANDIDATES => array_map(fn(\WordPress\AiClient\Results\DTO\Candidate $candidate) => $candidate->toArray(), $this->candidates), self::KEY_TOKEN_USAGE => $this->tokenUsage->toArray(), self::KEY_PROVIDER_METADATA => $this->providerMetadata->toArray(), self::KEY_MODEL_METADATA => $this->modelMetadata->toArray(), self::KEY_ADDITIONAL_DATA => $this->additionalData]; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function fromArray(array $array): self { static::validateFromArrayData($array, [self::KEY_ID, self::KEY_CANDIDATES, self::KEY_TOKEN_USAGE, self::KEY_PROVIDER_METADATA, self::KEY_MODEL_METADATA]); $candidates = array_map(fn(array $candidateData) => \WordPress\AiClient\Results\DTO\Candidate::fromArray($candidateData), $array[self::KEY_CANDIDATES]); return new self($array[self::KEY_ID], $candidates, \WordPress\AiClient\Results\DTO\TokenUsage::fromArray($array[self::KEY_TOKEN_USAGE]), ProviderMetadata::fromArray($array[self::KEY_PROVIDER_METADATA]), ModelMetadata::fromArray($array[self::KEY_MODEL_METADATA]), $array[self::KEY_ADDITIONAL_DATA] ?? []); } /** * Performs a deep clone of the result. * * This method ensures that all nested objects (candidates, token usage, metadata) * are cloned to prevent modifications to the cloned result from affecting the original. * * @since 0.4.2 */ public function __clone() { $clonedCandidates = []; foreach ($this->candidates as $candidate) { $clonedCandidates[] = clone $candidate; } $this->candidates = $clonedCandidates; $this->tokenUsage = clone $this->tokenUsage; $this->providerMetadata = clone $this->providerMetadata; $this->modelMetadata = clone $this->modelMetadata; } } �����������������Results/DTO/TokenUsage.php��������������������������������������������������������������������������0000644�����������������00000011420�15250554011�0011405 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Results\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; /** * Represents token usage statistics for an AI operation. * * This DTO tracks the number of tokens used in prompts and completions, * which is important for monitoring usage and costs. * * Note that thought tokens are a subset of completion tokens, not additive. * In other words: completionTokens - thoughtTokens = tokens of actual output content. * * @since 0.1.0 * * @phpstan-type TokenUsageArrayShape array{ * promptTokens: int, * completionTokens: int, * totalTokens: int, * thoughtTokens?: int * } * * @extends AbstractDataTransferObject<TokenUsageArrayShape> */ class TokenUsage extends AbstractDataTransferObject { public const KEY_PROMPT_TOKENS = 'promptTokens'; public const KEY_COMPLETION_TOKENS = 'completionTokens'; public const KEY_TOTAL_TOKENS = 'totalTokens'; public const KEY_THOUGHT_TOKENS = 'thoughtTokens'; /** * @var int Number of tokens in the prompt. */ private int $promptTokens; /** * @var int Number of tokens in the completion, including any thought tokens. */ private int $completionTokens; /** * @var int Total number of tokens used. */ private int $totalTokens; /** * @var int|null Number of tokens used for thinking, as a subset of completion tokens. */ private ?int $thoughtTokens; /** * Constructor. * * @since 0.1.0 * * @param int $promptTokens Number of tokens in the prompt. * @param int $completionTokens Number of tokens in the completion, including any thought tokens. * @param int $totalTokens Total number of tokens used. * @param int|null $thoughtTokens Number of tokens used for thinking, as a subset of completion tokens. */ public function __construct(int $promptTokens, int $completionTokens, int $totalTokens, ?int $thoughtTokens = null) { $this->promptTokens = $promptTokens; $this->completionTokens = $completionTokens; $this->totalTokens = $totalTokens; $this->thoughtTokens = $thoughtTokens; } /** * Gets the number of prompt tokens. * * @since 0.1.0 * * @return int The prompt token count. */ public function getPromptTokens(): int { return $this->promptTokens; } /** * Gets the number of completion tokens, including any thought tokens. * * @since 0.1.0 * * @return int The completion token count. */ public function getCompletionTokens(): int { return $this->completionTokens; } /** * Gets the total number of tokens. * * @since 0.1.0 * * @return int The total token count. */ public function getTotalTokens(): int { return $this->totalTokens; } /** * Gets the number of thought tokens, which is a subset of the completion token count. * * @since 1.3.0 * * @return int|null The thought token count or null if not available. */ public function getThoughtTokens(): ?int { return $this->thoughtTokens; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_PROMPT_TOKENS => ['type' => 'integer', 'description' => 'Number of tokens in the prompt.'], self::KEY_COMPLETION_TOKENS => ['type' => 'integer', 'description' => 'Number of tokens in the completion, including any thought tokens.'], self::KEY_TOTAL_TOKENS => ['type' => 'integer', 'description' => 'Total number of tokens used.'], self::KEY_THOUGHT_TOKENS => ['type' => 'integer', 'description' => 'Number of tokens used for thinking, as a subset of completion tokens.']], 'required' => [self::KEY_PROMPT_TOKENS, self::KEY_COMPLETION_TOKENS, self::KEY_TOTAL_TOKENS]]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return TokenUsageArrayShape */ public function toArray(): array { $data = [self::KEY_PROMPT_TOKENS => $this->promptTokens, self::KEY_COMPLETION_TOKENS => $this->completionTokens, self::KEY_TOTAL_TOKENS => $this->totalTokens]; if ($this->thoughtTokens !== null) { $data[self::KEY_THOUGHT_TOKENS] = $this->thoughtTokens; } return $data; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function fromArray(array $array): self { static::validateFromArrayData($array, [self::KEY_PROMPT_TOKENS, self::KEY_COMPLETION_TOKENS, self::KEY_TOTAL_TOKENS]); return new self($array[self::KEY_PROMPT_TOKENS], $array[self::KEY_COMPLETION_TOKENS], $array[self::KEY_TOTAL_TOKENS], $array[self::KEY_THOUGHT_TOKENS] ?? null); } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Results/Contracts/ResultInterface.php���������������������������������������������������������������0000644�����������������00000002571�15250554011�0013760 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Results\Contracts; use WordPress\AiClient\Providers\DTO\ProviderMetadata; use WordPress\AiClient\Providers\Models\DTO\ModelMetadata; use WordPress\AiClient\Results\DTO\TokenUsage; /** * Interface for AI operation results. * * Results contain the output from AI operations along with metadata * such as token usage and provider-specific information. * * @since 0.1.0 */ interface ResultInterface { /** * Gets the result ID. * * @since 0.1.0 * * @return string The unique result identifier. */ public function getId(): string; /** * Gets token usage information. * * @since 0.1.0 * * @return TokenUsage Token usage statistics. */ public function getTokenUsage(): TokenUsage; /** * Gets the provider metadata. * * @since 0.1.0 * * @return ProviderMetadata The provider metadata. */ public function getProviderMetadata(): ProviderMetadata; /** * Gets the model metadata. * * @since 0.1.0 * * @return ModelMetadata The model metadata. */ public function getModelMetadata(): ModelMetadata; /** * Gets provider-specific metadata. * * @since 0.1.0 * * @return array<string, mixed> Provider metadata. */ public function getAdditionalData(): array; } ���������������������������������������������������������������������������������������������������������������������������������������Results/Enums/FinishReasonEnum.php������������������������������������������������������������������0000644�����������������00000002616�15250554011�0013225 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php declare (strict_types=1); namespace WordPress\AiClient\Results\Enums; use WordPress\AiClient\Common\AbstractEnum; /** * Enum for finish reasons of AI generation. * * @since 0.1.0 * * @method static self stop() Creates an instance for STOP reason. * @method static self length() Creates an instance for LENGTH reason. * @method static self contentFilter() Creates an instance for CONTENT_FILTER reason. * @method static self toolCalls() Creates an instance for TOOL_CALLS reason. * @method static self error() Creates an instance for ERROR reason. * @method bool isStop() Checks if the reason is STOP. * @method bool isLength() Checks if the reason is LENGTH. * @method bool isContentFilter() Checks if the reason is CONTENT_FILTER. * @method bool isToolCalls() Checks if the reason is TOOL_CALLS. * @method bool isError() Checks if the reason is ERROR. */ class FinishReasonEnum extends AbstractEnum { /** * Generation stopped naturally. */ public const STOP = 'stop'; /** * Generation stopped due to max length. */ public const LENGTH = 'length'; /** * Generation stopped due to content filter. */ public const CONTENT_FILTER = 'content_filter'; /** * Generation stopped to make tool calls. */ public const TOOL_CALLS = 'tool_calls'; /** * Generation stopped due to error. */ public const ERROR = 'error'; } ������������������������������������������������������������������������������������������������������������������Results/Enums/error_log�����������������������������������������������������������������������������0000644�����������������00000011312�15250554011�0011205 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������[15-Jul-2026 16:16:41 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php on line 23 [24-Jul-2026 12:15:16 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php on line 23 [25-Jul-2026 05:33:08 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php on line 23 [04-Aug-2026 23:27:54 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php on line 23 [13-Aug-2026 01:33:05 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php on line 23 [13-Aug-2026 13:00:07 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php on line 23 [19-Aug-2026 21:32:05 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php on line 23 [19-Aug-2026 22:05:23 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php on line 23 [19-Aug-2026 23:24:37 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php on line 23 [03-Sep-2026 06:49:43 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php on line 23 [04-Sep-2026 08:45:05 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php on line 23 [05-Sep-2026 20:46:32 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php on line 23 [10-Sep-2026 09:13:03 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php:23 Stack trace: #0 {main} thrown in /home/veronikagstoette/public_html/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php on line 23 ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������